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