/* | |
* 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.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); | |
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; | |
} | |
} |