| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.myfaces.renderkit.html; |
| |
| import java.io.IOException; |
| import java.util.Map; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| import jakarta.faces.context.FacesContext; |
| import jakarta.faces.context.ResponseWriter; |
| import jakarta.faces.lifecycle.ClientWindow; |
| import jakarta.faces.render.RenderKitFactory; |
| import jakarta.faces.render.ResponseStateManager; |
| |
| import org.apache.myfaces.application.viewstate.StateCache; |
| import org.apache.myfaces.renderkit.MyfacesResponseStateManager; |
| import org.apache.myfaces.config.MyfacesConfig; |
| import org.apache.myfaces.renderkit.html.util.HTML; |
| import org.apache.myfaces.spi.StateCacheProvider; |
| import org.apache.myfaces.spi.StateCacheProviderFactory; |
| |
| /** |
| * @author Manfred Geiler (latest modification by $Author$) |
| * @version $Revision$ $Date$ |
| */ |
| public class HtmlResponseStateManager extends MyfacesResponseStateManager |
| { |
| private static final Logger log = Logger.getLogger(HtmlResponseStateManager.class.getName()); |
| |
| private static final String VIEW_STATE_COUNTER = "oam.partial.VIEW_STATE_COUNTER"; |
| private static final String CLIENT_WINDOW_COUNTER = "oam.partial.CLIENT_WINDOW_COUNTER"; |
| |
| private static final String SESSION_TOKEN = "oam.rsm.SESSION_TOKEN"; |
| |
| private StateCacheProvider stateCacheFactory; |
| private MyfacesConfig myfacesConfig; |
| |
| public HtmlResponseStateManager() |
| { |
| FacesContext facesContext = FacesContext.getCurrentInstance(); |
| myfacesConfig = MyfacesConfig.getCurrentInstance(facesContext.getExternalContext()); |
| |
| stateCacheFactory = StateCacheProviderFactory |
| .getStateCacheProviderFactory(facesContext.getExternalContext()) |
| .getStateCacheProvider(facesContext.getExternalContext()); |
| } |
| |
| @Override |
| public void writeState(FacesContext facesContext, Object state) throws IOException |
| { |
| ResponseWriter responseWriter = facesContext.getResponseWriter(); |
| |
| Object savedStateObject = null; |
| |
| if (!facesContext.getViewRoot().isTransient()) |
| { |
| // Only if the view is not transient needs to be saved |
| savedStateObject = getStateCache(facesContext).encodeSerializedState(facesContext, state); |
| } |
| |
| // write the view state field |
| writeViewStateField(facesContext, responseWriter, savedStateObject); |
| |
| // renderKitId field |
| writeRenderKitIdField(facesContext, responseWriter); |
| |
| // windowId field |
| writeWindowIdField(facesContext, responseWriter); |
| } |
| |
| private void writeWindowIdField(FacesContext facesContext, ResponseWriter responseWriter) throws IOException |
| { |
| ClientWindow clientWindow = facesContext.getExternalContext().getClientWindow(); |
| if (clientWindow != null) |
| { |
| responseWriter.startElement(HTML.INPUT_ELEM, null); |
| responseWriter.writeAttribute(HTML.TYPE_ATTR, HTML.INPUT_TYPE_HIDDEN, null); |
| responseWriter.writeAttribute(HTML.ID_ATTR, generateUpdateClientWindowId(facesContext), null); |
| responseWriter.writeAttribute(HTML.NAME_ATTR, ResponseStateManager.CLIENT_WINDOW_PARAM, null); |
| responseWriter.writeAttribute(HTML.VALUE_ATTR, clientWindow.getId(), null); |
| responseWriter.endElement(HTML.INPUT_ELEM); |
| } |
| } |
| |
| @Override |
| public void saveState(FacesContext facesContext, Object state) |
| { |
| if (!facesContext.getViewRoot().isTransient()) |
| { |
| getStateCache(facesContext).saveSerializedView(facesContext, state); |
| } |
| } |
| |
| private void writeViewStateField(FacesContext facesContext, ResponseWriter responseWriter, Object savedState) |
| throws IOException |
| { |
| String serializedState = getStateCache(facesContext).getStateTokenProcessor(facesContext) |
| .encode(facesContext, savedState); |
| |
| if (log.isLoggable(Level.FINE)) |
| { |
| log.fine("Writing serialized viewstate string with hashCode : " + serializedState.hashCode()); |
| } |
| |
| responseWriter.startElement(HTML.INPUT_ELEM, null); |
| responseWriter.writeAttribute(HTML.TYPE_ATTR, HTML.INPUT_TYPE_HIDDEN, null); |
| responseWriter.writeAttribute(HTML.NAME_ATTR, ResponseStateManager.VIEW_STATE_PARAM, null); |
| if (myfacesConfig.isRenderViewStateId()) |
| { |
| // responseWriter.writeAttribute(HTML.ID_ATTR, STANDARD_STATE_SAVING_PARAM, null); |
| // JSF 2.2 if jakarta.faces.ViewState is used as the id, in portlet |
| // case it will be duplicate ids and that not xml friendly. |
| responseWriter.writeAttribute(HTML.ID_ATTR, |
| HtmlResponseStateManager.generateUpdateViewStateId(facesContext), |
| null); |
| } |
| responseWriter.writeAttribute(HTML.VALUE_ATTR, serializedState, null); |
| if (myfacesConfig.isAutocompleteOffViewState()) |
| { |
| responseWriter.writeAttribute(HTML.AUTOCOMPLETE_ATTR, "off", null); |
| } |
| responseWriter.endElement(HTML.INPUT_ELEM); |
| } |
| |
| private void writeRenderKitIdField(FacesContext facesContext, ResponseWriter responseWriter) throws IOException |
| { |
| |
| String defaultRenderKitId = facesContext.getApplication().getDefaultRenderKitId(); |
| if (defaultRenderKitId != null && !RenderKitFactory.HTML_BASIC_RENDER_KIT.equals(defaultRenderKitId)) |
| { |
| responseWriter.startElement(HTML.INPUT_ELEM, null); |
| responseWriter.writeAttribute(HTML.TYPE_ATTR, HTML.INPUT_TYPE_HIDDEN, null); |
| responseWriter.writeAttribute(HTML.NAME_ATTR, ResponseStateManager.RENDER_KIT_ID_PARAM, null); |
| responseWriter.writeAttribute(HTML.VALUE_ATTR, defaultRenderKitId, null); |
| responseWriter.endElement(HTML.INPUT_ELEM); |
| } |
| } |
| |
| @Override |
| public Object getState(FacesContext facesContext, String viewId) |
| { |
| Object savedState = getSavedState(facesContext); |
| if (savedState == null) |
| { |
| return null; |
| } |
| |
| return getStateCache(facesContext).restoreSerializedView(facesContext, viewId, savedState); |
| } |
| |
| /** |
| * Reconstructs the state from the "jakarta.faces.ViewState" request parameter. |
| * |
| * @param facesContext |
| * the current FacesContext |
| * |
| * @return the reconstructed state, or <code>null</code> if there was no saved state |
| */ |
| private Object getSavedState(FacesContext facesContext) |
| { |
| Object encodedState = |
| facesContext.getExternalContext().getRequestParameterMap().get(ResponseStateManager.VIEW_STATE_PARAM); |
| if(encodedState==null || (((String) encodedState).length() == 0)) |
| { |
| return null; |
| } |
| |
| Object savedStateObject = getStateCache(facesContext).getStateTokenProcessor(facesContext) |
| .decode(facesContext, (String)encodedState); |
| |
| return savedStateObject; |
| } |
| |
| /** |
| * Checks if the current request is a postback |
| * |
| * @since 1.2 |
| */ |
| @Override |
| public boolean isPostback(FacesContext context) |
| { |
| return context.getExternalContext().getRequestParameterMap().containsKey(ResponseStateManager.VIEW_STATE_PARAM); |
| } |
| |
| @Override |
| public String getViewState(FacesContext facesContext, Object baseState) |
| { |
| // If the view is transient, baseState is null, so it should return null. |
| // In this way, PartialViewContext will skip <update ...> section related |
| // to view state (stateless view does not have state, so it does not need |
| // to update the view state section). |
| if (baseState == null) |
| { |
| return null; |
| } |
| if (facesContext.getViewRoot().isTransient()) |
| { |
| return null; |
| } |
| |
| Object state = getStateCache(facesContext).saveSerializedView(facesContext, baseState); |
| |
| return getStateCache(facesContext).getStateTokenProcessor(facesContext).encode(facesContext, state); |
| } |
| |
| @Override |
| public boolean isStateless(FacesContext context, String viewId) |
| { |
| if (context.isPostback()) |
| { |
| String encodedState = |
| context.getExternalContext().getRequestParameterMap().get(ResponseStateManager.VIEW_STATE_PARAM); |
| if (encodedState == null || ((String) encodedState).length() == 0) |
| { |
| return false; |
| } |
| |
| return getStateCache(context).getStateTokenProcessor(context).isStateless(context, encodedState); |
| } |
| else |
| { |
| // "... java.lang.IllegalStateException - if this method is invoked |
| // and the statefulness of the preceding call to writeState( |
| // jakarta.faces.context.FacesContext, java.lang.Object) cannot be determined. |
| throw new IllegalStateException( |
| "Cannot decide if the view is stateless or not, since the request is " |
| + "not postback (no preceding writeState(...))."); |
| } |
| } |
| |
| @Override |
| public String getCryptographicallyStrongTokenFromSession(FacesContext context) |
| { |
| Map<String, Object> sessionMap = context.getExternalContext().getSessionMap(); |
| String savedToken = (String) sessionMap.get(SESSION_TOKEN); |
| if (savedToken == null) |
| { |
| savedToken = getStateCache(context).createCryptographicallyStrongTokenFromSession(context); |
| sessionMap.put(SESSION_TOKEN, savedToken); |
| } |
| return savedToken; |
| } |
| |
| @Override |
| public boolean isWriteStateAfterRenderViewRequired(FacesContext facesContext) |
| { |
| return getStateCache(facesContext).isWriteStateAfterRenderViewRequired(facesContext); |
| } |
| |
| protected StateCache getStateCache(FacesContext facesContext) |
| { |
| return stateCacheFactory.getStateCache(facesContext); |
| } |
| |
| public static String generateUpdateClientWindowId(FacesContext facesContext) |
| { |
| // JSF 2.2 section 2.2.6.1 Partial State Rendering |
| // According to the javascript doc of faces.ajax.response, |
| // |
| // The new syntax looks like this: |
| // <update id="<VIEW_ROOT_CONTAINER_CLIENT_ID><SEP>jakarta.faces.ClientWindow<SEP><UNIQUE_PER_VIEW_NUMBER>"> |
| // <![CDATA[...]]> |
| // </update> |
| // |
| // UNIQUE_PER_VIEW_NUMBER aim for portlet case. In that case it is possible to have |
| // multiple sections for update. In servlet case there is only one update section per |
| // ajax request. |
| |
| char separator = facesContext.getNamingContainerSeparatorChar(); |
| |
| Integer count = (Integer) facesContext.getAttributes().get(CLIENT_WINDOW_COUNTER); |
| if (count == null) |
| { |
| count = Integer.valueOf(1); |
| } |
| else |
| { |
| count += 1; |
| } |
| facesContext.getAttributes().put(CLIENT_WINDOW_COUNTER, count); |
| |
| String id = facesContext.getViewRoot().getContainerClientId(facesContext) + |
| separator + ResponseStateManager.CLIENT_WINDOW_PARAM + separator + count; |
| return id; |
| } |
| |
| public static String generateUpdateViewStateId(FacesContext facesContext) |
| { |
| // JSF 2.2 section 2.2.6.1 Partial State Rendering |
| // According to the javascript doc of faces.ajax.response, |
| // |
| // The new syntax looks like this: |
| // <update id="<VIEW_ROOT_CONTAINER_CLIENT_ID><SEP>jakarta.faces.ViewState<SEP><UNIQUE_PER_VIEW_NUMBER>"> |
| // <![CDATA[...]]> |
| // </update> |
| // |
| // UNIQUE_PER_VIEW_NUMBER aim for portlet case. In that case it is possible to have |
| // multiple sections for update. In servlet case there is only one update section per |
| // ajax request. |
| |
| char separator = facesContext.getNamingContainerSeparatorChar(); |
| |
| Integer count = (Integer) facesContext.getAttributes().get(VIEW_STATE_COUNTER); |
| if (count == null) |
| { |
| count = Integer.valueOf(1); |
| } |
| else |
| { |
| count += 1; |
| } |
| facesContext.getAttributes().put(VIEW_STATE_COUNTER, count); |
| |
| String id = facesContext.getViewRoot().getContainerClientId(facesContext) + |
| separator + ResponseStateManager.VIEW_STATE_PARAM + separator + count; |
| return id; |
| } |
| } |