/*
 * 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.application.ViewHandler;
import javax.faces.application.ViewHandlerWrapper;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;

/**
 * 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 ViewHandlerWrapper
{
    private final ViewHandler original;



    /**
     * Constructor.
     */
    public UrlParameterViewHandler(final ViewHandler original)
    {
        this.original = original;
    }

    public ViewHandler getWrapped()
    {
        return original;
    }

    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);
    }

    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;
    }
}