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.tobago.internal.lifecycle;
21  
22  import org.apache.myfaces.tobago.config.TobagoConfig;
23  import org.apache.myfaces.tobago.internal.component.AbstractUIPage;
24  import org.apache.myfaces.tobago.portlet.PortletUtils;
25  import org.apache.myfaces.tobago.renderkit.TobagoResponseStateManager;
26  import org.apache.myfaces.tobago.util.ComponentUtils;
27  import org.apache.myfaces.tobago.webapp.Secret;
28  import org.slf4j.Logger;
29  import org.slf4j.LoggerFactory;
30  
31  import javax.faces.FacesException;
32  import javax.faces.application.Application;
33  import javax.faces.application.FacesMessage;
34  import javax.faces.application.ViewHandler;
35  import javax.faces.component.UIComponent;
36  import javax.faces.component.UIInput;
37  import javax.faces.component.UIViewRoot;
38  import javax.faces.context.ExternalContext;
39  import javax.faces.context.FacesContext;
40  import javax.faces.el.ValueBinding;
41  import javax.faces.event.PhaseId;
42  import java.io.IOException;
43  import java.lang.reflect.Method;
44  import java.util.Iterator;
45  import java.util.List;
46  import java.util.Map;
47  
48  /**
49   * Implements the life cycle as described in Spec. 1.0 PFD Chapter 2
50   * <p/>
51   * Restore view phase (JSF Spec 2.2.1)
52   */
53  class RestoreViewExecutor implements PhaseExecutor {
54  
55    private static final Logger LOG = LoggerFactory.getLogger(RestoreViewExecutor.class);
56  
57    public boolean execute(FacesContext facesContext) {
58      ExternalContext externalContext = facesContext.getExternalContext();
59  
60      Map sessionMap = externalContext.getSessionMap();
61      UIViewRoot viewRoot = (UIViewRoot) sessionMap.get(TobagoLifecycle.VIEW_ROOT_KEY);
62      if (viewRoot != null) {
63        facesContext.setViewRoot(viewRoot);
64        sessionMap.remove(TobagoLifecycle.VIEW_ROOT_KEY);
65        //noinspection unchecked
66        List<Object[]> messageHolders = (List<Object[]>) sessionMap.get(TobagoLifecycle.FACES_MESSAGES_KEY);
67        if (messageHolders != null) {
68          for (Object[] messageHolder : messageHolders) {
69            facesContext.addMessage((String) messageHolder[0], (FacesMessage) messageHolder[1]);
70          }
71        }
72        sessionMap.remove(TobagoLifecycle.FACES_MESSAGES_KEY);
73        if (viewRoot.getChildCount() > 0 && viewRoot.getChildren().get(0) instanceof AbstractUIPage) {
74          viewRoot.getChildren().get(0).decode(facesContext);
75        }
76        facesContext.renderResponse();
77        return true;
78      }
79  
80      if (facesContext.getViewRoot() != null) {
81        facesContext.getViewRoot().setLocale(facesContext.getExternalContext().getRequestLocale());
82        ComponentUtils.resetPage(facesContext);
83        recursivelyHandleComponentReferencesAndSetValid(facesContext, facesContext.getViewRoot());
84        return false;
85      }
86  
87      // Derive view identifier
88      String viewId = deriveViewId(facesContext);
89  
90      if (viewId == null) {
91  
92        if (externalContext.getRequestServletPath() == null) {
93          return true;
94        }
95  
96        if (!externalContext.getRequestServletPath().endsWith("/")) {
97          try {
98            externalContext.redirect(externalContext.getRequestServletPath() + "/");
99            facesContext.responseComplete();
100           return true;
101         } catch (IOException e) {
102           throw new FacesException("redirect failed", e);
103         }
104       }
105     }
106 
107     Application application = facesContext.getApplication();
108     ViewHandler viewHandler = application.getViewHandler();
109 
110     boolean postBack = isPostBack(facesContext);
111     if (postBack) {
112       viewRoot = viewHandler.restoreView(facesContext, viewId);
113     }
114     if (viewRoot == null) {
115       viewRoot = viewHandler.createView(facesContext, viewId);
116       viewRoot.setViewId(viewId);
117       facesContext.renderResponse();
118     }
119 
120     facesContext.setViewRoot(viewRoot);
121     ComponentUtils.resetPage(facesContext);
122 
123     if (!postBack) {
124       // no POST or query parameters --> set render response flag
125       facesContext.renderResponse();
126     }
127 
128     if (!isSessionSecretValid(facesContext)) {
129       if (LOG.isDebugEnabled()) {
130         LOG.debug("Secret is invalid!");
131       }
132       facesContext.renderResponse();
133     }
134 
135     recursivelyHandleComponentReferencesAndSetValid(facesContext, viewRoot);
136     //noinspection unchecked
137     facesContext.getExternalContext().getRequestMap().put(TobagoLifecycle.VIEW_ROOT_KEY, viewRoot);
138     return false;
139   }
140 
141   private boolean isPostBack(FacesContext facesContext) {
142     Map requestParameterMap = facesContext.getExternalContext().getRequestParameterMap();
143     return requestParameterMap.containsKey(TobagoResponseStateManager.VIEW_STATE_PARAM);
144   }
145 
146   private boolean isSessionSecretValid(FacesContext facesContext) {
147     if (TobagoConfig.getInstance(FacesContext.getCurrentInstance()).isCheckSessionSecret()) {
148       return Secret.check(facesContext);
149     } else {
150       return true;
151     }
152   }
153 
154   public PhaseId getPhase() {
155     return PhaseId.RESTORE_VIEW;
156   }
157 
158   private static String deriveViewId(FacesContext facesContext) {
159     ExternalContext externalContext = facesContext.getExternalContext();
160 
161     if (PortletUtils.isPortletRequest(facesContext)) {
162       return PortletUtils.getViewId(facesContext);
163     }
164 
165     String viewId = externalContext.getRequestPathInfo(); // getPathInfo
166     if (viewId == null) {
167       // No extra path info found, so it is propably extension mapping
168       viewId = externalContext.getRequestServletPath(); // getServletPath
169       if (viewId == null) {
170         String msg = "RequestServletPath is null, cannot determine viewId of current page.";
171         LOG.error(msg);
172         throw new FacesException(msg);
173       }
174 
175       // TODO: JSF Spec 2.2.1 - what do they mean by "if the default
176       // ViewHandler implementation is used..." ?
177       String defaultSuffix = externalContext.getInitParameter(ViewHandler.DEFAULT_SUFFIX_PARAM_NAME);
178       String suffix = defaultSuffix != null ? defaultSuffix : ViewHandler.DEFAULT_SUFFIX;
179       if (suffix.charAt(0) != '.') {
180         String msg = "Default suffix must start with a dot!";
181         LOG.error(msg);
182         throw new FacesException(msg);
183       }
184 
185       int dot = viewId.lastIndexOf('.');
186       if (dot == -1) {
187         LOG.error("Assumed extension mapping, but there is no extension in " + viewId);
188         viewId = null;
189       } else {
190         viewId = viewId.substring(0, dot) + suffix;
191       }
192     }
193 
194     return viewId;
195   }
196 
197   // next two methods are taken from 'org.apache.myfaces.shared.util.RestoreStateUtils'
198 
199   public static void recursivelyHandleComponentReferencesAndSetValid(FacesContext facesContext,
200       UIComponent parent) {
201     boolean forceHandle = false;
202 
203     Method handleBindingsMethod = getBindingMethod(parent);
204 
205     if (handleBindingsMethod != null && !forceHandle) {
206       try {
207         handleBindingsMethod.invoke(parent, new Object[]{});
208       } catch (Throwable th) {
209         LOG.error("Exception while invoking handleBindings on component with client-id:"
210             + parent.getClientId(facesContext), th);
211       }
212     } else {
213       for (Iterator it = parent.getFacetsAndChildren(); it.hasNext();) {
214         UIComponent component = (UIComponent) it.next();
215 
216         ValueBinding binding = component.getValueBinding("binding");    //TODO: constant
217         if (binding != null && !binding.isReadOnly(facesContext)) {
218           binding.setValue(facesContext, component);
219         }
220 
221         if (component instanceof UIInput) {
222           ((UIInput) component).setValid(true);
223         }
224 
225         recursivelyHandleComponentReferencesAndSetValid(facesContext, component);
226       }
227     }
228   }
229 
230   /**
231    * This is all a hack to work around a spec-bug which will be fixed in JSF2.0
232    *
233    * @return true if this component is bindingAware (e.g. aliasBean)
234    */
235   private static Method getBindingMethod(UIComponent parent) {
236     Class[] interfaces = parent.getClass().getInterfaces();
237 
238     for (Class clazz : interfaces) {
239       if (clazz.getName().contains("BindingAware")) {
240         try {
241           return parent.getClass().getMethod("handleBindings", new Class[]{});
242         } catch (NoSuchMethodException e) {
243           // return
244         }
245       }
246     }
247 
248     return null;
249   }
250 
251 }