/*
* 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.orchestra.urlParamNav;
import javax.faces.FacesException;
import javax.faces.application.ViewHandler;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import org.apache.myfaces.orchestra.lib.OrchestraException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Locale;
/**
* Allow the to-view-id URL in a faces-config navigation case to include
* query parameters and EL expressions.
* <p>
* This class plays a few tricks to hide from the real NavigationHandler
* and ViewHandler classes the fact that a URL contains non-standard data.
* <p>
* This class also plays a few reflection-based tricks so that the code can
* be compiled against JSF1.1, and work with both JSF1.1 and JSF1.2. The
* code is a little fragile and will probably need to be updated to work
* correctly with JSF2.0, but that is the fault of the JSF spec.
*/
public class UrlParameterViewHandler extends ViewHandler
{
private static final Method CALC_CHAR_ENC_METHOD;
private static final Method INIT_VIEW_METHOD;
private final ViewHandler original;
/**
* Static initialization block.
*/
static
{
CALC_CHAR_ENC_METHOD = getMethodOpt(ViewHandler.class,
"calculateCharacterEncoding",
new Class[] {FacesContext.class});
INIT_VIEW_METHOD = getMethodOpt(ViewHandler.class,
"initView",
new Class[] {FacesContext.class});
}
/**
* If the specified class has a method with the specified name and params, return
* it else return null.
*/
private static Method getMethodOpt(Class clazz, String methodName, Class[] args)
{
try
{
return clazz.getMethod(methodName, args);
}
catch(NoSuchMethodException e)
{
return null;
}
}
/**
* Constructor.
*/
public UrlParameterViewHandler(final ViewHandler original)
{
this.original = original;
}
/**
* Delegate to wrapped instance.
* <p>
* This method was added in JSF1.2. We must therefore use reflection
* to invoke the method on the wrapped instance. Note that this method
* is never invoked unless this is a JSF1.2 environment.
*
* @since 1.3
*/
public java.lang.String calculateCharacterEncoding(FacesContext context)
{
try
{
Object ret = CALC_CHAR_ENC_METHOD.invoke(original, new Object[] {context});
return (String) ret;
}
catch(Exception e)
{
throw new OrchestraException("Unable to invoke calculateCharacterEncoding on wrapped ViewHandler");
}
}
/**
* Delegate to wrapped instance.
* <p>
* This method was added in JSF1.2. We must therefore use reflection
* to invoke the method on the wrapped instance. Note that this method
* is never invoked unless this is a JSF1.2 environment.
*
* @since 1.3
*/
public void initView(FacesContext context)
throws FacesException
{
try
{
INIT_VIEW_METHOD.invoke(original, new Object[] {context});
}
catch(Exception e)
{
throw new OrchestraException("Unable to invoke initView on wrapped ViewHandler");
}
}
public Locale calculateLocale(FacesContext context)
{
return original.calculateLocale(context);
}
public String calculateRenderKitId(FacesContext context)
{
return original.calculateRenderKitId(context);
}
public UIViewRoot createView(FacesContext context, String viewId)
{
return original.createView(context, viewId);
}
public String getActionURL(FacesContext context, String viewId)
{
if (viewId != null)
{
// Expand any EL expression in the URL.
//
// This handles a call from a NavigationHandler which is processing a redirect
// navigation case. A NavigationHandler must call the following in order:
// * ViewHandler.getActionURL,
// * ExternalContext.encodeActionURL
// * ExternalContext.redirect
//
// Orchestra hooks into ExternalContext.encodeActionURL to trigger the
// RequestParameterProviderManager which then inserts various query params
// into the URL.
//
// So here, ensure that any EL expressions are expanded before the
// RequestParameterProviderManager is invoked. An alternative would be for
// the RequestParameterProviderManager to do the encoding, but at the current
// time that class is not JSF-dependent in any way, so calling JSF expression
// expansion from there is not possible.
//
// Note that this method is also called from a Form component when rendering
// its 'action' attribute. This code therefore has the side-effect of
// permitting EL expressions in a form's action. This is not particularly
// useful, however, as they are expected to have been expanded before this
// method is invoked..
viewId = expandExpressions(context, viewId);
// Hide query parameters from the standard ViewHandlerImpl. The standard
// implementation of ViewHandlerImpl.getActionUrl method does not handle
// query params well. So strip them off, invoke the processing, then reattach
// them afterwards.
int pos = viewId.indexOf('?');
if (pos > -1)
{
String realViewId = viewId.substring(0, pos);
String params = viewId.substring(pos);
return original.getActionURL(context, realViewId) + params;
}
}
return original.getActionURL(context, viewId);
}
public String getResourceURL(FacesContext context, String path)
{
return original.getResourceURL(context, path);
}
public void renderView(FacesContext context, UIViewRoot viewToRender)
throws IOException, FacesException
{
original.renderView(context, viewToRender);
}
public UIViewRoot restoreView(FacesContext context, String viewId)
{
return original.restoreView(context, viewId);
}
public void writeState(FacesContext context)
throws IOException
{
original.writeState(context);
}
private static String expandExpressions(FacesContext context, String url)
{
int pos = url.indexOf("#{");
if (pos > -1 && url.indexOf("}", pos) > -1)
{
// There is at least one EL expression, so evaluate the whole url string.
// Note that something like "aaa#{foo}bbb#{bar}ccc" is fine; both the
// el expressions will get replaced.
ValueBinding vb = context.getApplication().createValueBinding(url);
return (String) vb.getValue(context);
}
return url;
}
}