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