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

import javax.faces.application.Application;
import javax.faces.component.StateHolder;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;

/**
 * Manually implement a proxy for Converter objects which can correctly
 * serialize a Converter instance that has been wrapped in an Orchestra proxy.
 * <p>
 * A custom converter may need access to orchestra scopes or Orchestra
 * persistence contexts. In these cases, it must be wrapped in the appropriate
 * Orchestra proxies. Unfortunately these proxies are not serializable;
 * they implement neither java.io.Serializable nor the JSF StateHolder interface.
 * Therefore when a view tree containing components that have such a converter
 * attached, a serialization failure will occur.
 * <p>
 * This class can be used to "wrap" such converter instances, making 
 * serialization work again. This class implements the JSF StateHolder
 * interface, and implements this by saving both its own state AND the
 * state of the Converter it proxies. In addition, the beanName used to
 * create the original converter instance is kept. When the view tree
 * is restored, JSF will automatically recreate an instance of this type
 * and restore its state; this then retrieves a new (proxied) instance of
 * the converter using the beanName, then invokes the restoreState method
 * on it passing the saved state data.
 * <p>
 * Note that if the converter has no internal state (other than that defined
 * in the bean definition) then it does not need to implement the StateHolder
 * interface; when the view tree is restored a new instance will be created
 * using the beanName. 
 * 
 * <h2>Using from an orchestra:converter tag</h2>
 * 
 * When the orchestra:converter tag is used, the fetched object is wrapped in
 * an instance of this type by default.
 * 
 * <h2>Using from a converter attribute</h2>
 * 
 * A component in a page can specify <code>converter="#{someBeanName}"</code>.
 * The definition for bean "someBeanName" should specify that a non-singleton
 * instance of this class should be created, and the "beanName" constructor
 * parameter should be set to refer to another bean-definition that is the
 * actual converter type to be instantiated.
 * <p>
 * When using Spring, a BeanPostProcessor class could also be defined that
 * intercepts creation of all Converter instances and automatically wraps
 * them in a SerializableConverter.
 * 
 * <h2>Further details on serialization</h2>
 *
 * When using client-side state saving, the view tree is serialized <i>by JSF</i>
 * at the end of each request, and sent to the user along with the generated output.
 * <p>
 * When using server-side state, the JSF implementation might use JSF serialization
 * to generate data to cache in the session, or might just store a reference to 
 * the unserialized component tree. The latter is not generally wise as switching
 * to client-side state saving later will suddenly change the way that the tree
 * is handled on save and postback and may expose bugs in component serialization
 * (particularly for custom components), so JSF implementations either default to
 * JSF-serialization even on server-side state, or do not even offer the option
 * to save an unserialized tree. 
 * <p>
 * If a servlet engine is configured for distributed sessions, then when a request
 * is handled by a different server than handled the last request, the session is
 * serialized on the host that handled the old request and deserialized on the host
 * handling the new request. 
 * <p>
 * Even without distributed sessions, a servlet engine will serialize sessions when
 * short of memory ("session passivation") and cache them on disk. Session serialization
 * also happens when a servlet engine is reconfigured for "hot restart", ie where the
 * server can be rebooted without losing user sessions.
 * <p>
 * With both the client-side or server-side with "normal" JSF serialization, JSF
 * will first try to serialize converters using the StateHolder methods, and only use
 * java.io.Serializable when that is not supported. Therefore having this class implement
 * StateHolder, and then requiring all converters used with this wrapper to implement
 * StateHolder solves the serialization issues. This class does not need to implement
 * java.io.Serializable because serialization is always done via the StateHolder methods
 * instead.
 * <p>
 * For applications where a raw JSF tree is stored in the session, then an attempt by
 * the server to serialize the session might trigger an attempt to use java.io.Serializable
 * apis on this object. As this does not implement the java.io.Serializable, an exception
 * will occur. This class cannot simply implement java.io.Serializable because the object
 * it references is usually proxied, and the proxies are not generally serialiuable. It
 * *might* be possible for this code to implement normal serialization by "unproxying"
 * the bean, invoking serialization on the real object, then on deserialize re-wrapping
 * the bean in proxies. However as this code is unlikely to ever be used, this has not
 * been implemented.
 * <p>
 * Hopefully in some later release, the Orchestra-generated proxies will be able to
 * correctly serialize themselves automatically. When that happens, this class will
 * no longer be needed.
 */
public class SerializableConverter implements Converter, StateHolder
{
    private static final long serialVersionUID = 2L;

    private String beanName;
    private transient Converter converter;
    private transient Object[] converterState;

    public SerializableConverter()
    {
        // setBeanName or restoreState must be called when this constructor is used 
    }

    public SerializableConverter(String beanName)
    {
        this.beanName = beanName;
    }

    public SerializableConverter(String beanName, Converter instance)
    {
        this.beanName = beanName;
        this.converter = instance;
    }

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

    protected Converter getConverter(FacesContext context)
    {
        if (this.converter == null)
        {
            Application application = context.getApplication();
            this.converter = (Converter) application.getVariableResolver().resolveVariable(context, beanName);
            
            if (converterState != null)
            {
                // see method restoreState
                ((StateHolder) converter).restoreState(context, converterState);

                // state no longer needed
                converterState = null;
            }

        }

        return this.converter;
    }

    public Object getAsObject(FacesContext context, UIComponent component, String value) throws ConverterException
    {
        return getConverter(context).getAsObject(context, component, value);
    }

    public String getAsString(FacesContext context, UIComponent component, Object value) throws ConverterException
    {
        return getConverter(context).getAsString(context, component, value);
    }

    // StateHolder methods

    public boolean isTransient()
    {
        return false;
    }

    public void restoreState(FacesContext context, Object savedState)
    {
        Object[] state = (Object[]) savedState;
        beanName = (String) state[0];

        if (state.length == 2)
        {
            // Ok, the converter must be a StateHolder...
            //
            // Ideally here we would just call getConverter() to obtain a converter
            // instance, then invoke its restoreState method immediately. However
            // there is a problem with that; in most cases the Converter instance
            // will be in view-controller scope, which means that the proxy created
            // for it needs to look up the bean that is the controller for the current
            // view and then map the bean into the same conversation as that bean.
            // Unfortunately looking up the controller for the current view requires
            // knowing the current viewId; that is available on the UIViewRoot object
            // - but the UIViewRoot doesn't exist yet as we are currently part-way
            // through the restore-view phase.
            //
            // The solution is ugly but effective: avoid calling getConverter here and
            // instead save the state data for later; on the first call to getConverter
            // do the state restoring then.
            converterState = (Object[]) state[1];
        }
    }

    public Object saveState(FacesContext context)
    {
        Object[] state;
        Converter c = getConverter(context);
        if (c instanceof StateHolder)
        {
            state = new Object[2];
            state[1] = ((StateHolder) c).saveState(context);
        }
        else
        {
            state = new Object[1];
        }
        state[0] = beanName;
        return state;
    }

    public void setTransient(boolean newTransientValue)
    {
    }
}
