blob: aa8f4aba2de8b3ff5035cdec560799ee73de8d6a [file] [log] [blame]
/*
* 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.application;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;
import javax.faces.FacesException;
import javax.faces.FactoryFinder;
import javax.faces.application.Application;
import javax.faces.application.StateManager;
import javax.faces.application.ViewHandler;
import javax.faces.application.ViewVisitOption;
import javax.faces.component.UIViewParameter;
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.push.PushContext;
import javax.faces.render.RenderKitFactory;
import javax.faces.render.ResponseStateManager;
import javax.faces.view.ViewDeclarationLanguage;
import javax.faces.view.ViewDeclarationLanguageFactory;
import javax.faces.view.ViewMetadata;
import javax.servlet.http.HttpServletResponse;
import org.apache.myfaces.application.viewstate.StateCacheUtils;
import org.apache.myfaces.util.lang.Assert;
import org.apache.myfaces.view.facelets.StateWriter;
/**
* JSF 2.0 ViewHandler implementation
*
* @since 2.0
*/
public class ViewHandlerImpl extends ViewHandler
{
private static final Logger log = Logger.getLogger(ViewHandlerImpl.class.getName());
public static final String FORM_STATE_MARKER = "<!--@@JSF_FORM_STATE_MARKER@@-->";
private ViewIdSupport _viewIdSupport;
private ViewDeclarationLanguageFactory _vdlFactory;
private Set<String> _protectedViewsSet;
private Set<String> _unmodifiableProtectedViewsSet;
/**
* Gets the current ViewHandler via FacesContext.getApplication().getViewHandler().
* We have to use this method to invoke any other specified ViewHandler-method
* in the code, because direct access (this.method()) will cause problems if
* the ViewHandler is wrapped.
* @param facesContext
* @return
*/
public static ViewHandler getViewHandler(FacesContext facesContext)
{
return facesContext.getApplication().getViewHandler();
}
public ViewHandlerImpl()
{
_protectedViewsSet = Collections.newSetFromMap(new ConcurrentHashMap<>());
_unmodifiableProtectedViewsSet = Collections.unmodifiableSet(_protectedViewsSet);
_vdlFactory = (ViewDeclarationLanguageFactory)
FactoryFinder.getFactory(FactoryFinder.VIEW_DECLARATION_LANGUAGE_FACTORY);
if (log.isLoggable(Level.FINEST))
{
log.finest("New ViewHandler instance created");
}
}
@Override
public String deriveViewId(FacesContext context, String rawViewId)
{
if(rawViewId != null)
{
try
{
return getViewIdSupport(context).deriveViewId(context, rawViewId);
}
catch (InvalidViewIdException e)
{
sendSourceNotFound(context, e.getMessage());
}
}
return rawViewId; // If the argument input is null, return null.
}
@Override
public String deriveLogicalViewId(FacesContext context, String rawViewId)
{
if(rawViewId != null)
{
try
{
return getViewIdSupport(context).deriveLogicalViewId(context, rawViewId);
}
catch (InvalidViewIdException e)
{
sendSourceNotFound(context, e.getMessage());
}
}
return rawViewId; // If the argument input is null, return null.
}
@Override
public String getBookmarkableURL(FacesContext context, String viewId,
Map<String, List<String>> parameters, boolean includeViewParams)
{
Map<String, List<String>> viewParameters;
if (includeViewParams)
{
viewParameters = getViewParameterList(context, viewId, parameters);
}
else
{
viewParameters = parameters;
}
// note that we cannot use this.getActionURL(), because this will
// cause problems if the ViewHandler is wrapped
String actionEncodedViewId = getViewHandler(context).getActionURL(context, viewId);
ExternalContext externalContext = context.getExternalContext();
String bookmarkEncodedURL = externalContext.encodeBookmarkableURL(actionEncodedViewId, viewParameters);
return externalContext.encodeActionURL(bookmarkEncodedURL);
}
@Override
public String getRedirectURL(FacesContext context, String viewId,
Map<String, List<String>> parameters, boolean includeViewParams)
{
Map<String, List<String>> viewParameters;
if (includeViewParams)
{
viewParameters = getViewParameterList(context, viewId, parameters);
}
else
{
viewParameters = parameters;
}
// note that we cannot use this.getActionURL(), because this will
// cause problems if the ViewHandler is wrapped
String actionEncodedViewId = getViewHandler(context).getActionURL(context, viewId);
ExternalContext externalContext = context.getExternalContext();
String redirectEncodedURL = externalContext.encodeRedirectURL(actionEncodedViewId, viewParameters);
return externalContext.encodeActionURL(redirectEncodedURL);
}
@Override
public ViewDeclarationLanguage getViewDeclarationLanguage(
FacesContext context, String viewId)
{
// return a suitable ViewDeclarationLanguage implementation for the given viewId
return _vdlFactory.getViewDeclarationLanguage(viewId);
}
@Override
public void initView(FacesContext context) throws FacesException
{
if(context.getExternalContext().getRequestCharacterEncoding() == null)
{
super.initView(context);
}
}
/**
* Get the locales specified as acceptable by the original request, compare them to the
* locales supported by this Application and return the best match.
*
* @param facesContext
*/
@Override
public Locale calculateLocale(FacesContext facesContext)
{
Application application = facesContext.getApplication();
for (Iterator<Locale> requestLocales = facesContext.getExternalContext().getRequestLocales(); requestLocales
.hasNext();)
{
Locale requestLocale = requestLocales.next();
for (Iterator<Locale> supportedLocales = application.getSupportedLocales(); supportedLocales.hasNext();)
{
Locale supportedLocale = supportedLocales.next();
// higher priority to a language match over an exact match
// that occurs further down (see JSTL Reference 1.0 8.3.1)
if (requestLocale.getLanguage().equals(supportedLocale.getLanguage())
&& (supportedLocale.getCountry() == null || supportedLocale.getCountry().length() == 0))
{
return supportedLocale;
}
else if (supportedLocale.equals(requestLocale))
{
return supportedLocale;
}
}
}
Locale defaultLocale = application.getDefaultLocale();
return defaultLocale != null ? defaultLocale : Locale.getDefault();
}
@Override
public String calculateRenderKitId(FacesContext facesContext)
{
Object renderKitId = facesContext.getExternalContext().getRequestMap().get(
ResponseStateManager.RENDER_KIT_ID_PARAM);
if (renderKitId == null)
{
renderKitId = facesContext.getApplication().getDefaultRenderKitId();
}
if (renderKitId == null)
{
renderKitId = RenderKitFactory.HTML_BASIC_RENDER_KIT;
}
return renderKitId.toString();
}
@Override
public UIViewRoot createView(FacesContext context, String viewId)
{
Assert.notNull(context, "facesContext");
String calculatedViewId = getViewIdSupport(context).deriveLogicalViewId(context, viewId);
// we cannot use this.getVDL() directly (see getViewHandler())
//return getViewHandler(context)
// .getViewDeclarationLanguage(context, calculatedViewId)
// .createView(context, calculatedViewId);
// -= Leonardo Uribe =- Temporally reverted by TCK issues.
ViewDeclarationLanguage vdl = getViewDeclarationLanguage(context,calculatedViewId);
if (vdl == null)
{
// If there is no VDL that can handle the view, throw 404 response.
sendSourceNotFound(context, viewId);
return null;
}
return vdl.createView(context,calculatedViewId);
}
@Override
public String getActionURL(FacesContext context, String viewId)
{
Assert.notNull(context, "facesContext");
Assert.notNull(viewId, "viewId");
return getViewIdSupport(context).calculateActionURL(context, viewId);
}
@Override
public String getResourceURL(FacesContext facesContext, String path)
{
Assert.notNull(facesContext, "facesContext");
Assert.notNull(path, "path");
if (path.length() > 0 && path.charAt(0) == '/')
{
String contextPath = facesContext.getExternalContext().getRequestContextPath();
if (contextPath == null)
{
return path;
}
else if (contextPath.length() == 1 && contextPath.charAt(0) == '/')
{
// If the context path is root, it is not necessary to append it, otherwise
// and extra '/' will be set.
return path;
}
else
{
return contextPath + path;
}
}
return path;
}
@Override
public void renderView(FacesContext context, UIViewRoot viewToRender)
throws IOException, FacesException
{
Assert.notNull(context, "context");
Assert.notNull(viewToRender, "viewToRender");
// we cannot use this.getVDL() directly (see getViewHandler())
//String viewId = viewToRender.getViewId();
//getViewHandler(context).getViewDeclarationLanguage(context, viewId)
// .renderView(context, viewToRender);
// -= Leonardo Uribe =- Temporally reverted by TCK issues.
getViewDeclarationLanguage(context,viewToRender.getViewId()).renderView(context, viewToRender);
}
@Override
public UIViewRoot restoreView(FacesContext context, String viewId)
{
Assert.notNull(context, "context");
String calculatedViewId = getViewIdSupport(context).deriveLogicalViewId(context, viewId);
// we cannot use this.getVDL() directly (see getViewHandler())
//return getViewHandler(context)
// .getViewDeclarationLanguage(context,calculatedViewId)
// .restoreView(context, calculatedViewId);
// -= Leonardo Uribe =- Temporally reverted by TCK issues.
ViewDeclarationLanguage vdl = getViewDeclarationLanguage(context,calculatedViewId);
if (vdl == null)
{
// If there is no VDL that can handle the view, throw 404 response.
sendSourceNotFound(context, viewId);
return null;
}
return vdl.restoreView(context, calculatedViewId);
}
@Override
public void writeState(FacesContext context) throws IOException
{
Assert.notNull(context, "context");
if(context.getPartialViewContext().isAjaxRequest())
{
return;
}
ResponseStateManager responseStateManager = context.getRenderKit().getResponseStateManager();
setWritingState(context, responseStateManager);
StateManager stateManager = context.getApplication().getStateManager();
// By the spec, it is necessary to use a writer to write FORM_STATE_MARKER,
// after the view is rendered, to preserve changes done on the component tree
// on rendering time. But if server side state saving is used, this is not
// really necessary, because a token could be used and after the view is
// rendered, a simple call to StateManager.saveState() could do the trick.
// The code below check if we are using MyFacesResponseStateManager and if
// that so, check if the current one support the trick.
if (StateCacheUtils.isMyFacesResponseStateManager(responseStateManager))
{
if (StateCacheUtils.getMyFacesResponseStateManager(responseStateManager).
isWriteStateAfterRenderViewRequired(context))
{
// Only write state marker if javascript view state is disabled
context.getResponseWriter().write(FORM_STATE_MARKER);
}
else
{
stateManager.writeState(context, new Object[2]);
}
}
else
{
// Only write state marker if javascript view state is disabled
context.getResponseWriter().write(FORM_STATE_MARKER);
}
}
@Override
public void addProtectedView(String urlPattern)
{
_protectedViewsSet.add(urlPattern);
}
@Override
public boolean removeProtectedView(String urlPattern)
{
return _protectedViewsSet.remove(urlPattern);
}
@Override
public Set<String> getProtectedViewsUnmodifiable()
{
return _unmodifiableProtectedViewsSet;
}
private void setWritingState(FacesContext context, ResponseStateManager rsm)
{
// Facelets specific hack:
// Tell the StateWriter that we're about to write state
StateWriter stateWriter = StateWriter.getCurrentInstance(context);
if (stateWriter != null)
{
// Write the STATE_KEY out. Unfortunately, this will
// be wasteful for pure server-side state managers where nothing
// is actually written into the output, but this cannot
// programatically be discovered
// -= Leonardo Uribe =- On MyFacesResponseStateManager was added
// some methods to discover it programatically.
if (StateCacheUtils.isMyFacesResponseStateManager(rsm))
{
if (StateCacheUtils.getMyFacesResponseStateManager(rsm).isWriteStateAfterRenderViewRequired(context))
{
stateWriter.writingState();
}
else
{
stateWriter.writingStateWithoutWrapper();
}
}
else
{
stateWriter.writingState();
}
}
//else
//{
//we're in a JSP, let the JSPStatemanager know that we need to actually write the state
//}
}
private Map<String, List<String>> getViewParameterList(FacesContext context,
String viewId, Map<String, List<String>> parametersFromArg)
{
UIViewRoot viewRoot = context.getViewRoot();
String currentViewId = viewRoot.getViewId();
Collection<UIViewParameter> toViewParams = null;
Collection<UIViewParameter> currentViewParams = ViewMetadata.getViewParameters(viewRoot);
if (currentViewId.equals(viewId))
{
toViewParams = currentViewParams;
}
else
{
String calculatedViewId = getViewIdSupport(context).deriveLogicalViewId(context, viewId);
// we cannot use this.getVDL() directly (see getViewHandler())
//ViewDeclarationLanguage vdl = getViewHandler(context).
// getViewDeclarationLanguage(context, calculatedViewId);
// -= Leonardo Uribe =- Temporally reverted by TCK issues.
ViewDeclarationLanguage vdl = getViewDeclarationLanguage(context,calculatedViewId);
ViewMetadata viewMetadata = vdl.getViewMetadata(context, viewId);
// getViewMetadata() returns null on JSP
if (viewMetadata != null)
{
UIViewRoot viewFromMetaData = viewMetadata.createMetadataView(context);
toViewParams = ViewMetadata.getViewParameters(viewFromMetaData);
}
}
if (toViewParams == null || toViewParams.isEmpty())
{
return parametersFromArg;
}
// we need to use a custom Map to add the view parameters,
// otherwise the current value of the view parameter will be added to
// the navigation case as a static (!!!) parameter, thus the value
// won't be updated on any following request
// (Note that parametersFromArg is the Map from the NavigationCase)
// Also note that we don't have to copy the Lists, because they won't be changed
Map<String, List<String>> parameters = new HashMap<>(parametersFromArg);
for (UIViewParameter viewParameter : toViewParams)
{
if (!parameters.containsKey(viewParameter.getName()))
{
String parameterValue = viewParameter.getStringValueFromModel(context);
if (parameterValue == null)
{
if(currentViewId.equals(viewId))
{
parameterValue = viewParameter.getStringValue(context);
}
else
{
if (viewParameter.getName() != null)
{
for (UIViewParameter curParam : currentViewParams)
{
if (viewParameter.getName().equals(curParam.getName()))
{
parameterValue = curParam.getStringValue(context);
break;
}
}
}
}
}
if (parameterValue != null)
{
// since we have checked !parameters.containsKey(viewParameter.getName())
// here already, the parameters Map will never contain a List under the
// key viewParameter.getName(), thus we do not have to check it here (again).
List<String> parameterValueList = new ArrayList<>(1);
parameterValueList.add(parameterValue);
parameters.put(viewParameter.getName(), parameterValueList);
}
}
}
return parameters;
}
private void sendSourceNotFound(FacesContext context, String message)
{
HttpServletResponse response = (HttpServletResponse) context.getExternalContext().getResponse();
try
{
context.responseComplete();
response.sendError(HttpServletResponse.SC_NOT_FOUND, message);
}
catch (IOException ioe)
{
throw new FacesException(ioe);
}
}
public void setViewIdSupport(ViewIdSupport viewIdSupport)
{
_viewIdSupport = viewIdSupport;
}
protected ViewIdSupport getViewIdSupport(FacesContext context)
{
if (_viewIdSupport == null)
{
_viewIdSupport = ViewIdSupport.getInstance(context);
}
return _viewIdSupport;
}
@Override
public Stream<String> getViews(FacesContext facesContext, String path, int maxDepth, ViewVisitOption... options)
{
Stream concatenatedStream = null;
for (ViewDeclarationLanguage vdl : _vdlFactory.getAllViewDeclarationLanguages())
{
Stream stream = vdl.getViews(facesContext, path, maxDepth, options);
if (concatenatedStream == null)
{
concatenatedStream = stream;
}
else
{
concatenatedStream = Stream.concat(concatenatedStream, stream);
}
}
return concatenatedStream == null ? Stream.empty() : concatenatedStream;
}
@Override
public String getWebsocketURL(FacesContext context, String channelAndToken)
{
String url = context.getExternalContext().getRequestContextPath() +
PushContext.URI_PREFIX + "/" + channelAndToken;
return url;
}
}