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  package org.apache.myfaces.renderkit.html;
20  
21  import java.io.IOException;
22  import java.util.Map;
23  import java.util.logging.Logger;
24  
25  import javax.faces.context.ExternalContext;
26  import javax.faces.context.FacesContext;
27  import javax.faces.context.ResponseWriter;
28  import javax.faces.lifecycle.ClientWindow;
29  import javax.faces.render.RenderKitFactory;
30  import javax.faces.render.ResponseStateManager;
31  
32  import org.apache.myfaces.application.StateCache;
33  import org.apache.myfaces.application.StateCacheFactory;
34  import org.apache.myfaces.application.viewstate.StateCacheFactoryImpl;
35  import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
36  import org.apache.myfaces.renderkit.MyfacesResponseStateManager;
37  import org.apache.myfaces.renderkit.StateTokenProcessor;
38  import org.apache.myfaces.shared.config.MyfacesConfig;
39  import org.apache.myfaces.shared.renderkit.html.HTML;
40  import org.apache.myfaces.shared.util.StateUtils;
41  
42  /**
43   * @author Manfred Geiler (latest modification by $Author: lu4242 $)
44   * @version $Revision: 1541762 $ $Date: 2013-11-13 18:56:47 -0500 (Wed, 13 Nov 2013) $
45   */
46  public class HtmlResponseStateManager extends MyfacesResponseStateManager
47  {
48      private static final Logger log = Logger.getLogger(HtmlResponseStateManager.class.getName());
49  
50      public static final String STANDARD_STATE_SAVING_PARAM = "javax.faces.ViewState";
51      
52      private static final String VIEW_STATE_COUNTER = "oam.partial.VIEW_STATE_COUNTER";
53      private static final String CLIENT_WINDOW_COUNTER = "oam.partial.CLIENT_WINDOW_COUNTER";
54      
55      private static final String SESSION_TOKEN = "oam.rsm.SESSION_TOKEN";
56  
57      /**
58       * Define if the state caching code should be handled by the ResponseStateManager or by the StateManager used.
59       * <p>
60       * This param is used to keep compatibility with previous state managers implementations depending from old myfaces
61       * way to deal with this. For example, JspStateManagerImpl requires this param set to false, but by default 
62       * it is set to true, to keep aligned with the Reference Implementation (RI). Note also the default StateManagerImpl
63       * requires this property set to true in order to work correctly, so if you set this param to false, please
64       * remember to add an entry into your faces-config.xml setting up JspStateManagerImpl as the state manager to use.
65       * </p> 
66       * @deprecated 
67       */
68      @Deprecated
69      @JSFWebConfigParam(since="2.0.6", expectedValues="true, false", defaultValue="true", group="state",
70          deprecated=true)
71      public static final String INIT_PARAM_HANDLE_STATE_CACHING_MECHANICS
72              = "org.apache.myfaces.HANDLE_STATE_CACHING_MECHANICS";
73      
74      private StateCacheFactory _stateCacheFactory;
75      
76      private StateTokenProcessor _stateTokenProcessor;
77      
78      public HtmlResponseStateManager()
79      {
80          _stateCacheFactory = new StateCacheFactoryImpl();
81          _stateTokenProcessor = new DefaultStateTokenProcessor();
82      }
83      
84      @Override
85      public void writeState(FacesContext facesContext, Object state) throws IOException
86      {
87          ResponseWriter responseWriter = facesContext.getResponseWriter();
88  
89          Object savedStateObject = null;
90          
91          if (!facesContext.getViewRoot().isTransient())
92          {
93              // Only if the view is not transient needs to be saved
94              savedStateObject = getStateCache(facesContext).encodeSerializedState(facesContext, state);
95          }
96  
97          // write the view state field
98          writeViewStateField(facesContext, responseWriter, savedStateObject);
99  
100         // renderKitId field
101         writeRenderKitIdField(facesContext, responseWriter);
102         
103         // windowId field
104         writeWindowIdField(facesContext, responseWriter);
105     }
106     
107     private void writeWindowIdField(FacesContext facesContext, ResponseWriter responseWriter) throws IOException
108     {
109         ClientWindow clientWindow = facesContext.getExternalContext().getClientWindow();
110         if (clientWindow != null)
111         {
112             responseWriter.startElement(HTML.INPUT_ELEM, null);
113             responseWriter.writeAttribute(HTML.TYPE_ATTR, HTML.INPUT_TYPE_HIDDEN, null);
114             responseWriter.writeAttribute(HTML.ID_ATTR, generateUpdateClientWindowId(facesContext), null);
115             responseWriter.writeAttribute(HTML.NAME_ATTR, ResponseStateManager.CLIENT_WINDOW_PARAM, null);
116             responseWriter.writeAttribute(HTML.VALUE_ATTR, clientWindow.getId(), null);
117             responseWriter.endElement(HTML.INPUT_ELEM);
118         }
119     }
120     
121     @Override
122     public void saveState(FacesContext facesContext, Object state)
123     {
124         if (!facesContext.getViewRoot().isTransient())
125         {
126             getStateCache(facesContext).saveSerializedView(facesContext, state);
127         }
128     }
129 
130     private void writeViewStateField(FacesContext facesContext, ResponseWriter responseWriter, Object savedState)
131         throws IOException
132     {
133         String serializedState = _stateTokenProcessor.encode(facesContext, savedState);
134         ExternalContext extContext = facesContext.getExternalContext();
135         MyfacesConfig myfacesConfig = MyfacesConfig.getCurrentInstance(extContext);
136 
137         responseWriter.startElement(HTML.INPUT_ELEM, null);
138         responseWriter.writeAttribute(HTML.TYPE_ATTR, HTML.INPUT_TYPE_HIDDEN, null);
139         responseWriter.writeAttribute(HTML.NAME_ATTR, STANDARD_STATE_SAVING_PARAM, null);
140         if (myfacesConfig.isRenderViewStateId())
141         {
142             // responseWriter.writeAttribute(HTML.ID_ATTR, STANDARD_STATE_SAVING_PARAM, null);
143             // JSF 2.2 if javax.faces.ViewState is used as the id, in portlet
144             // case it will be duplicate ids and that not xml friendly.
145             responseWriter.writeAttribute(HTML.ID_ATTR,
146                 HtmlResponseStateManager.generateUpdateViewStateId(
147                     facesContext), null);
148         }
149         responseWriter.writeAttribute(HTML.VALUE_ATTR, serializedState, null);
150         responseWriter.endElement(HTML.INPUT_ELEM);
151     }
152 
153     private void writeRenderKitIdField(FacesContext facesContext, ResponseWriter responseWriter) throws IOException
154     {
155 
156         String defaultRenderKitId = facesContext.getApplication().getDefaultRenderKitId();
157         if (defaultRenderKitId != null && !RenderKitFactory.HTML_BASIC_RENDER_KIT.equals(defaultRenderKitId))
158         {
159             responseWriter.startElement(HTML.INPUT_ELEM, null);
160             responseWriter.writeAttribute(HTML.TYPE_ATTR, HTML.INPUT_TYPE_HIDDEN, null);
161             responseWriter.writeAttribute(HTML.NAME_ATTR, ResponseStateManager.RENDER_KIT_ID_PARAM, null);
162             responseWriter.writeAttribute(HTML.VALUE_ATTR, defaultRenderKitId, null);
163             responseWriter.endElement(HTML.INPUT_ELEM);
164         }
165     }
166 
167     @Override
168     public Object getState(FacesContext facesContext, String viewId)
169     {
170         Object savedState = getSavedState(facesContext);
171         if (savedState == null)
172         {
173             return null;
174         }
175 
176         return getStateCache(facesContext).restoreSerializedView(facesContext, viewId, savedState);
177     }
178 
179     /**
180      * Reconstructs the state from the "javax.faces.ViewState" request parameter.
181      * 
182      * @param facesContext
183      *            the current FacesContext
184      * 
185      * @return the reconstructed state, or <code>null</code> if there was no saved state
186      */
187     private Object getSavedState(FacesContext facesContext)
188     {
189         Object encodedState = 
190             facesContext.getExternalContext().getRequestParameterMap().get(STANDARD_STATE_SAVING_PARAM);
191         if(encodedState==null || (((String) encodedState).length() == 0))
192         {
193             return null;
194         }
195 
196         Object savedStateObject = _stateTokenProcessor.decode(facesContext, (String)encodedState);
197         
198         return savedStateObject;
199     }
200 
201     /**
202      * Checks if the current request is a postback
203      * 
204      * @since 1.2
205      */
206     @Override
207     public boolean isPostback(FacesContext context)
208     {
209         return context.getExternalContext().getRequestParameterMap().containsKey(ResponseStateManager.VIEW_STATE_PARAM);
210     }
211 
212     @Override
213     public String getViewState(FacesContext facesContext, Object baseState)
214     {
215         // If the view is transient, baseState is null, so it should return null.
216         // In this way, PartialViewContext will skip <update ...> section related
217         // to view state (stateless view does not have state, so it does not need
218         // to update the view state section). 
219         if (baseState == null)
220         {
221             return null;
222         }
223         if (facesContext.getViewRoot().isTransient())
224         {
225             return null;
226         }
227         
228         Object state = getStateCache(facesContext).saveSerializedView(facesContext, baseState);
229 
230         return _stateTokenProcessor.encode(facesContext, state);
231     }
232 
233     @Override
234     public boolean isStateless(FacesContext context, String viewId)
235     {
236         if (context.isPostback())
237         {
238             String encodedState = 
239                 context.getExternalContext().getRequestParameterMap().get(STANDARD_STATE_SAVING_PARAM);
240             if(encodedState==null || (((String) encodedState).length() == 0))
241             {
242                 return false;
243             }
244 
245             return _stateTokenProcessor.isStateless(context, encodedState);
246         }
247         else 
248         {
249             // "... java.lang.IllegalStateException - if this method is invoked 
250             // and the statefulness of the preceding call to writeState(
251             // javax.faces.context.FacesContext, java.lang.Object) cannot be determined.
252             throw new IllegalStateException(
253                 "Cannot decide if the view is stateless or not, since the request is "
254                 + "not postback (no preceding writeState(...)).");
255         }
256     }
257 
258     @Override
259     public String getCryptographicallyStrongTokenFromSession(FacesContext context)
260     {
261         Map<String, Object> sessionMap = context.getExternalContext().getSessionMap();
262         String savedToken = (String) sessionMap.get(SESSION_TOKEN);
263         if (savedToken == null)
264         {
265             savedToken = getStateCache(context).createCryptographicallyStrongTokenFromSession(context);
266             sessionMap.put(SESSION_TOKEN, savedToken);
267         }
268         return savedToken;
269     }
270     
271     @Override
272     public boolean isWriteStateAfterRenderViewRequired(FacesContext facesContext)
273     {
274         return getStateCache(facesContext).isWriteStateAfterRenderViewRequired(facesContext);
275     }
276 
277     protected StateCache getStateCache(FacesContext facesContext)
278     {
279         return _stateCacheFactory.getStateCache(facesContext);
280     }
281 
282     public static String generateUpdateClientWindowId(FacesContext facesContext)
283     {
284         // JSF 2.2 section 2.2.6.1 Partial State Rendering
285         // According to the javascript doc of jsf.ajax.response,
286         //
287         // The new syntax looks like this:
288         // <update id="<VIEW_ROOT_CONTAINER_CLIENT_ID><SEP>javax.faces.ClientWindow<SEP><UNIQUE_PER_VIEW_NUMBER>">
289         //    <![CDATA[...]]>
290         // </update>
291         //
292         // UNIQUE_PER_VIEW_NUMBER aim for portlet case. In that case it is possible to have
293         // multiple sections for update. In servlet case there is only one update section per
294         // ajax request.
295         
296         String id;
297         char separator = facesContext.getNamingContainerSeparatorChar();
298         Integer count = (Integer) facesContext.getAttributes().get(CLIENT_WINDOW_COUNTER);
299         if (count == null)
300         {
301             count = Integer.valueOf(0);
302         }
303         count += 1;
304         id = facesContext.getViewRoot().getContainerClientId(facesContext) + 
305             separator + ResponseStateManager.CLIENT_WINDOW_PARAM + separator + count;
306         facesContext.getAttributes().put(CLIENT_WINDOW_COUNTER, count);
307         return id;
308     }
309     
310     public static String generateUpdateViewStateId(FacesContext facesContext)
311     {
312         // JSF 2.2 section 2.2.6.1 Partial State Rendering
313         // According to the javascript doc of jsf.ajax.response,
314         //
315         // The new syntax looks like this:
316         // <update id="<VIEW_ROOT_CONTAINER_CLIENT_ID><SEP>javax.faces.ViewState<SEP><UNIQUE_PER_VIEW_NUMBER>">
317         //    <![CDATA[...]]>
318         // </update>
319         //
320         // UNIQUE_PER_VIEW_NUMBER aim for portlet case. In that case it is possible to have
321         // multiple sections for update. In servlet case there is only one update section per
322         // ajax request.
323         
324         String id;
325         char separator = facesContext.getNamingContainerSeparatorChar();
326         Integer count = (Integer) facesContext.getAttributes().get(VIEW_STATE_COUNTER);
327         if (count == null)
328         {
329             count = Integer.valueOf(0);
330         }
331         count += 1;
332         id = facesContext.getViewRoot().getContainerClientId(facesContext) + 
333             separator + ResponseStateManager.VIEW_STATE_PARAM + separator + count;
334         facesContext.getAttributes().put(VIEW_STATE_COUNTER, count);
335         return id;
336     }
337 
338     private static class DefaultStateTokenProcessor extends StateTokenProcessor
339     {
340         private static final String STATELESS_TOKEN = "stateless";
341 
342         @Override
343         public Object decode(FacesContext facesContext, String token)
344         {
345             if (STATELESS_TOKEN.equals(token))
346             {
347                 // Should not happen, because ResponseStateManager.isStateless(context,viewId) should
348                 // catch it first
349                 return null;
350             }
351             Object savedStateObject = StateUtils.reconstruct((String)token, facesContext.getExternalContext());
352             return savedStateObject;
353         }
354 
355         @Override
356         public String encode(FacesContext facesContext, Object savedStateObject)
357         {
358             if (facesContext.getViewRoot().isTransient())
359             {
360                 return STATELESS_TOKEN;
361             }
362             String serializedState = StateUtils.construct(savedStateObject, facesContext.getExternalContext());
363             return serializedState;
364         }
365 
366         @Override
367         public boolean isStateless(FacesContext facesContext, String token)
368         {
369             return STATELESS_TOKEN.equals(token);
370         }
371     }
372 }