View Javadoc

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