/*
 * 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.conversation.jsf.components;

import javax.faces.application.Application;
import javax.faces.component.UIComponent;
import javax.faces.component.ValueHolder;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.webapp.UIComponentTag;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.Tag;
import javax.servlet.jsp.tagext.TagSupport;

import org.apache.myfaces.orchestra.lib.jsf.SerializableConverter;

/**
 * Works like f:converter except that the converter instance is a managed-bean
 * instance rather than a simple class.
 * <p>
 * In addition, the retrieved Converter instance is (by default) wrapped in
 * a SerializableConverter instance. See the documentation for that class
 * for further details.
 * <p>
 * This is not actually orchestra-specific functionality; this is something
 * that the JSF core library could offer, or an add-on library such as Tomahawk.
 * But at the current time, no common library offers this feature and the
 * use of a SerializableConverter wrapper is very convenient.
 * <p>
 * The primary use-case for this tag is custom Converter classes that access
 * conversation state. For example, a Converter may be written to convert
 * an object instance into a string by reading its primary key, and on
 * postback convert the string (a primary key) back into an object 
 * instance by loading it using the persistence context associated with
 * a particular conversation. Of course such a converter must be configured
 * with the appropriate Orchestra scope, and therefore must be loaded as a
 * managed bean rather than via the standard f:converter mechanism.
 * <p>
 * An alternative to using this tag is simply to use the standard
 * "converter" attribute available on most JSF component tags, but if
 * a SerializableConverter wrapper is desired then two bean definitions are
 * needed rather than just one; see class SerializableConverter for details.
 * <p>
 * <h2>Creating custom converter tags</h2>
 * 
 * If you have written a custom Converter instance that can be configured
 * then configuration may be done via the managed-bean system. However it
 * can also be nice to configure instances via the tag, like the standard
 * f:dateTimeConverter or f:numberConverter. To do this, you can:
 * <ul>
 * <li>subclass this tag
 * <li>add setters for all the properties that you want configurable via the tag
 * <li>override the createConverter method and copy the tag properties onto the
 * newly created converter instance.
 * <li>implement the StateHolder interface on the Converter class in order to
 * save and restore these custom properties.
 * </ul>
 */
public class ConverterTag extends TagSupport
{
    private static final long serialVersionUID = 1L;
    private String beanName;
    private boolean useWrapper = true;

    public ConverterTag()
    {
        super();
    }

    public void setBeanName(String beanName)
    {
        this.beanName = beanName;
    }

    public void setUseWrapper(boolean enabled)
    {
        this.useWrapper = enabled;
    }

    public int doStartTag()
            throws JspException
    {
        UIComponentTag componentTag = UIComponentTag.getParentUIComponentTag(pageContext);
        if (componentTag == null)
        {
            throw new JspException("no parent UIComponentTag found");
        }
        if (!componentTag.getCreated())
        {
            return Tag.SKIP_BODY;
        }

        Converter converter = createConverter(beanName);
        
        if (useWrapper && (converter instanceof SerializableConverter == false))
        {
            // Needed to check if it is already of the specified type in case the
            // managed-bean framework has been configured to auto-wrap Converter
            // instances already (eg via a Spring BeanPostProcessor or equivalent).
            // This isn't the case, so wrap it now.
            converter = new SerializableConverter(beanName, converter);
        }

        UIComponent component = componentTag.getComponentInstance();
        if (component == null)
        {
            throw new JspException("parent UIComponentTag has no UIComponent");
        }
        if (!(component instanceof ValueHolder))
        {
            throw new JspException("UIComponent is no ValueHolder");
        }
        ((ValueHolder)component).setConverter(converter);

        return Tag.SKIP_BODY;
    }

    public void release()
    {
        super.release();
        beanName = null;
    }

    /**
     * Override this method in order to customise the bean instance.
     */
    protected static Converter createConverter(String beanName)
            throws JspException
    {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        Application application = facesContext.getApplication();
        Object converter = application.getVariableResolver().resolveVariable(facesContext, beanName);
        return (Converter) converter;
    }
}
