View Javadoc

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