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