View Javadoc

1   /*
2    * $Id: RestoreViewInterceptor.java 651946 2008-04-27 13:41:38Z apetrelli $
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  package org.apache.struts2.jsf;
23  
24  import java.io.IOException;
25  import java.lang.reflect.Method;
26  import java.util.Iterator;
27  
28  import javax.faces.FacesException;
29  import javax.faces.application.Application;
30  import javax.faces.application.ViewHandler;
31  import javax.faces.component.UIComponent;
32  import javax.faces.component.UIInput;
33  import javax.faces.component.UIViewRoot;
34  import javax.faces.context.ExternalContext;
35  import javax.faces.context.FacesContext;
36  import javax.faces.el.ValueBinding;
37  import javax.faces.event.PhaseId;
38  
39  /***
40   * Restores the view or component tree
41   */
42  public class RestoreViewInterceptor extends FacesInterceptor {
43  
44      private static final long serialVersionUID = -1500785113037140668L;
45  
46      /***
47       * Restore View (JSF.2.2.1)
48       *
49       * @param viewId
50       *            The view id
51       * @param facesContext
52       *            The faces context
53       * @return true, if immediate rendering should occur
54       */
55      protected boolean executePhase(String viewId, FacesContext facesContext) {
56          boolean skipFurtherProcessing = false;
57          if (log.isTraceEnabled())
58              log.trace("entering restoreView");
59  
60          informPhaseListenersBefore(facesContext, PhaseId.RESTORE_VIEW);
61  
62          try {
63              if (isResponseComplete(facesContext, "restoreView", true)) {
64                  // have to skips this phase
65                  return true;
66              }
67              if (shouldRenderResponse(facesContext, "restoreView", true)) {
68                  skipFurtherProcessing = true;
69              }
70  
71              ExternalContext externalContext = facesContext.getExternalContext();
72              String defaultSuffix = externalContext
73                      .getInitParameter(ViewHandler.DEFAULT_SUFFIX_PARAM_NAME);
74              String suffix = defaultSuffix != null ? defaultSuffix
75                      : ViewHandler.DEFAULT_SUFFIX;
76              if (viewId != null) {
77                  viewId += suffix;
78              }
79  
80              if (viewId == null) {
81                  if (!externalContext.getRequestServletPath().endsWith("/")) {
82                      try {
83                          externalContext.redirect(externalContext
84                                  .getRequestServletPath()
85                                  + "/");
86                          facesContext.responseComplete();
87                          return true;
88                      } catch (IOException e) {
89                          throw new FacesException("redirect failed", e);
90                      }
91                  }
92              }
93  
94              Application application = facesContext.getApplication();
95              ViewHandler viewHandler = application.getViewHandler();
96  
97              // boolean viewCreated = false;
98              UIViewRoot viewRoot = viewHandler.restoreView(facesContext, viewId);
99              if (viewRoot == null) {
100                 viewRoot = viewHandler.createView(facesContext, viewId);
101                 viewRoot.setViewId(viewId);
102                 facesContext.renderResponse();
103                 // viewCreated = true;
104             }
105 
106             facesContext.setViewRoot(viewRoot);
107 
108             /*
109              * This section has been disabled because it causes some bug. Be
110              * careful if you need to re-enable it. Furthermore, for an unknown
111              * reason, it seems that by default it is executed (i.e.
112              * log.isTraceEnabled() is true). Bug example : This traceView
113              * causes DebugUtil.printComponent to print all the attributes of
114              * the view components. And if you have a data table within an
115              * aliasBean, this causes the data table to initialize it's value
116              * attribute while the alias isn't set. So, the value initializes
117              * with an UIData.EMPTY_DATA_MODEL, and not with the aliased one.
118              * But as it's initialized, it will not try to get the value from
119              * the ValueBinding next time it needs to. I expect this to cause
120              * more similar bugs. TODO : Completely remove or be SURE by default
121              * it's not executed, and it has no more side-effects.
122              *
123              * if (log.isTraceEnabled()) { //Note: DebugUtils Logger must also
124              * be in trace level DebugUtils.traceView(viewCreated ? "Newly
125              * created view" : "Restored view"); }
126              */
127 
128             if (facesContext.getExternalContext().getRequestParameterMap()
129                     .isEmpty()) {
130                 // no POST or query parameters --> set render response flag
131                 facesContext.renderResponse();
132             }
133 
134             recursivelyHandleComponentReferencesAndSetValid(facesContext,
135                     viewRoot);
136         } finally {
137             informPhaseListenersAfter(facesContext, PhaseId.RESTORE_VIEW);
138         }
139 
140         if (isResponseComplete(facesContext, "restoreView", false)
141                 || shouldRenderResponse(facesContext, "restoreView", false)) {
142             // since this phase is completed we don't need to return right away
143             // even if the response is completed
144             skipFurtherProcessing = true;
145         }
146 
147         if (!skipFurtherProcessing && log.isTraceEnabled())
148             log.trace("exiting restoreView ");
149         return skipFurtherProcessing;
150     }
151 
152     /***
153      * Walk the component tree, executing any component-bindings to reattach
154      * components to their backing beans. Also, any UIInput component is marked
155      * as Valid.
156      * <p>
157      * Note that this method effectively breaks encapsulation; instead of asking
158      * each component to update itself and its children, this method just
159      * reaches into each component. That makes it impossible for any component
160      * to customise its behaviour at this point.
161      * <p>
162      * This has been filed as an issue against the spec. Until this issue is
163      * resolved, we'll add a new marker-interface for components to allow them
164      * to define their interest in handling children bindings themselves.
165      */
166     protected void recursivelyHandleComponentReferencesAndSetValid(
167             FacesContext facesContext, UIComponent parent) {
168         recursivelyHandleComponentReferencesAndSetValid(facesContext, parent,
169                 false);
170     }
171 
172     protected void recursivelyHandleComponentReferencesAndSetValid(
173             FacesContext facesContext, UIComponent parent, boolean forceHandle) {
174         Method handleBindingsMethod = getBindingMethod(parent);
175 
176         if (handleBindingsMethod != null && !forceHandle) {
177             try {
178                 handleBindingsMethod.invoke(parent, new Object[] {});
179             } catch (Throwable th) {
180                 log.error(
181                         "Exception while invoking handleBindings on component with client-id:"
182                                 + parent.getClientId(facesContext), th);
183             }
184         } else {
185             for (Iterator it = parent.getFacetsAndChildren(); it.hasNext();) {
186                 UIComponent component = (UIComponent) it.next();
187 
188                 ValueBinding binding = component.getValueBinding("binding"); // TODO:
189                 // constant
190                 if (binding != null && !binding.isReadOnly(facesContext)) {
191                     binding.setValue(facesContext, component);
192                 }
193 
194                 if (component instanceof UIInput) {
195                     ((UIInput) component).setValid(true);
196                 }
197 
198                 recursivelyHandleComponentReferencesAndSetValid(facesContext,
199                         component);
200             }
201         }
202     }
203 
204     /***
205      * This is all a hack to work around a spec-bug which will be fixed in
206      * JSF2.0
207      *
208      * @param parent
209      * @return true if this component is bindingAware (e.g. aliasBean)
210      */
211     private static Method getBindingMethod(UIComponent parent) {
212         Class[] clazzes = parent.getClass().getInterfaces();
213 
214         for (int i = 0; i < clazzes.length; i++) {
215             Class clazz = clazzes[i];
216 
217             if (clazz.getName().indexOf("BindingAware") != -1) {
218                 try {
219                     return parent.getClass().getMethod("handleBindings",
220                             new Class[] {});
221                 } catch (NoSuchMethodException e) {
222                     // return
223                 }
224             }
225         }
226 
227         return null;
228     }
229 }