View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.myfaces.orchestra.flow.components;
21  
22  import javax.faces.FacesException;
23  import javax.faces.component.UIComponent;
24  import javax.faces.component.UIComponentBase;
25  import javax.faces.component.UIViewRoot;
26  import javax.faces.context.FacesContext;
27  
28  /**
29   * A component which handles clearing of cached user input (submittedValue) from
30   * input components whose model has been modified by an invoked flow.
31   * <p>
32   * When a flowcall is triggered by an immediate component, data entered by the user
33   * into input components is not pushed into the model before the call; it just gets
34   * stored in the "submittedValue" property of the input components. This view tree
35   * is then cached, and on return from the flow the original tree is deliberately restored
36   * so that user-entered data is not lost.
37   * <p>
38   * However this is a problem for input components whose model value has been modified by
39   * the flowcall (ie which map to something updated by a return-value from the flow). In
40   * this case we *do* want to discard the submittedValue in the component so that the
41   * new value in the model is displayed.
42   * <p>
43   * There is no automatic way to detect which input components in a page are affected by
44   * the return parameters from a flow. It *is* possible to detect input components whose
45   * value attribute is an EL expression that is identical to an EL expression for a return
46   * value, and that might be implemented in future. However there are many cases where a
47   * dependency cannot be detected with this simple test.
48   * <p>
49   * Instances of this component can be added to the page to explicitly mark components that
50   * need to be cleared on return from a specific flowcall.
51   * <p>
52   * Clearing of the submittedValue obviously does NOT need to be done on the first render
53   * of a view; it is something that is only relevant when a view has had at least one
54   * postback cycle and is then being re-rendered. And it is not relevant when a flowcall
55   * is triggered by a non-immediate command-component; in that case there is no _submittedValue
56   * cached in any input component, so updated model values cannot be "hidden" by the
57   * componenent cached value. In fact in this case, the original view is not even cached at
58   * all, as it can simply be recreated.
59   * <p>
60   * This component cannot be used with input components that are within a dataTable component;
61   * in that case the cached submitted-values are actually stored secretly within the dataTable
62   * and simply cannot be accessed by this component. In that case, the only solution is to
63   * apply this component to the entire table, ie clear all user data in all input fields
64   * within the table on flow return. This is not perfect, but is the best that can be done.
65   * <p>
66   * Note that this component does not simply "reset" the component; it actually deletes the
67   * component from the tree and relies on the rendering phase to recreate a new clean instance.
68   * This is done because UIInput has a resetValue method, but UIData does not.
69   */
70  
71  public class ClearOnCommit extends UIComponentBase
72  {
73      public static final String COMPONENT_FAMILY = "javax.faces.Component";
74      public static final String COMPONENT_TYPE = "org.apache.myfaces.orchestra.flow.components.ClearOnCommit";
75  
76      private String outcome;
77      private String target;
78  
79      @Override
80      public String getFamily()
81      {
82          return COMPONENT_FAMILY;
83      }
84  
85      /**
86       * The navigation outcome that causes this component to remove the target component
87       * from the component tree.
88       * <p>
89       * Static value only (EL expressions not supported).
90       */
91      public String getOutcome()
92      {
93          return outcome;
94      }
95  
96      public void setOutcome(String outcome)
97      {
98          this.outcome = outcome;
99      }
100 
101     /**
102      * Return the JSF component id of the target component to clear.
103      * <p>
104      * Static value only (EL expressions not supported).
105      */
106     public String getTarget()
107     {
108         return target;
109     }
110 
111     public void setTarget(String target)
112     {
113         this.target = target;
114     }
115 
116     /**
117      * If the specified navigation-outcome matches the outcome attribute of this component
118      * then delete the associated target component.
119      * <p>
120      * If this component has no "outcome" property set, then the target component is
121      * cleared on any commit.
122      */
123     private void clearTargetComponent(String outcome)
124     {
125         if ((this.outcome != null) && !this.outcome.equals(outcome))
126         {
127             return;
128         }
129 
130         if (this.getChildCount() > 0)
131         {
132             this.getChildren().clear();
133         }
134         
135         if (target != null)
136         {
137             UIComponent targetComponent = this.findComponent(target);
138             if (targetComponent == null)
139             {
140                 throw new FacesException("Target component for clearOnCommit does not exist:" + target);
141             }
142             targetComponent.getParent().getChildren().remove(targetComponent);
143         }
144     }
145 
146     // ================ State Methods =================
147 
148     @Override
149     public void restoreState(FacesContext context, Object state)
150     throws FacesException
151     {
152         Object[] states = (Object[]) state;
153         super.restoreState(context, states[0]);
154         outcome = (String) states[1];
155         target = (String) states[2];
156     }
157 
158     @Override
159     public Object saveState(FacesContext context)
160     {
161         return new Object[]
162             {
163                 super.saveState(context),
164                 outcome,
165                 target
166             };
167     }
168 
169     // ============ Static methods =================
170 
171     /**
172      * Execute the clearTargetComponent method of each ClearOnCommit component in the specified view tree.
173      * <p>
174      * This clears any _submittedValue property from the "target" component of each clearOnCommit component
175      * in the tree which has an outcome that matches the specified outcome value.
176      */
177     public static void executeAllInstances(UIViewRoot viewRoot, String outcome)
178     {
179         applyToAll(viewRoot, outcome);
180     }
181 
182     private static void applyToAll(UIComponent c, String outcome)
183     {
184         if (c instanceof ClearOnCommit)
185         {
186             ((ClearOnCommit) c).clearTargetComponent(outcome);
187         }
188 
189         if (c.getChildCount() == 0)
190         {
191             return;
192         }
193 
194         for(UIComponent child: c.getChildren())
195         {
196             applyToAll(child, outcome);
197         }
198     }
199 }