View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one
    3    * or more contributor license agreements.  See the NOTICE file
    4    * distributed with this work for additional information
    5    * regarding copyright ownership.  The ASF licenses this file
    6    * to you under the Apache License, Version 2.0 (the
    7    * "License"); you may not use this file except in compliance
    8    * with the License.  You may obtain a copy of the License at
    9    *
    10   *   http://www.apache.org/licenses/LICENSE-2.0
    11   *
    12   * Unless required by applicable law or agreed to in writing,
    13   * software distributed under the License is distributed on an
    14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    15   * KIND, either express or implied.  See the License for the
    16   * specific language governing permissions and limitations
    17   * under the License.
    18   */
    19  package org.apache.myfaces.orchestra.lib.jsf;
    20
    21  import javax.faces.application.Application;
    22  import javax.faces.component.StateHolder;
    23  import javax.faces.component.UIComponent;
    24  import javax.faces.context.FacesContext;
    25  import javax.faces.convert.Converter;
    26  import javax.faces.convert.ConverterException;
    27
    28  /**
    29   * Manually implement a proxy for Converter objects which can correctly
    30   * serialize a Converter instance that has been wrapped in an Orchestra proxy.
    31   * <p>
    32   * A custom converter may need access to orchestra scopes or Orchestra
    33   * persistence contexts. In these cases, it must be wrapped in the appropriate
    34   * Orchestra proxies. Unfortunately these proxies are not serializable;
    35   * they implement neither java.io.Serializable nor the JSF StateHolder interface.
    36   * Therefore when a view tree containing components that have such a converter
    37   * attached, a serialization failure will occur.
    38   * <p>
    39   * This class can be used to "wrap" such converter instances, making
    40   * serialization work again. This class implements the JSF StateHolder
    41   * interface, and implements this by saving both its own state AND the
    42   * state of the Converter it proxies. In addition, the beanName used to
    43   * create the original converter instance is kept. When the view tree
    44   * is restored, JSF will automatically recreate an instance of this type
    45   * and restore its state; this then retrieves a new (proxied) instance of
    46   * the converter using the beanName, then invokes the restoreState method
    47   * on it passing the saved state data.
    48   * <p>
    49   * Note that if the converter has no internal state (other than that defined
    50   * in the bean definition) then it does not need to implement the StateHolder
    51   * interface; when the view tree is restored a new instance will be created
    52   * using the beanName.
    53   *
    54   * <h2>Using from an orchestra:converter tag</h2>
    55   *
    56   * When the orchestra:converter tag is used, the fetched object is wrapped in
    57   * an instance of this type by default.
    58   *
    59   * <h2>Using from a converter attribute</h2>
    60   *
    61   * A component in a page can specify <code>converter="#{someBeanName}"</code>.
    62   * The definition for bean "someBeanName" should specify that a non-singleton
    63   * instance of this class should be created, and the "beanName" constructor
    64   * parameter should be set to refer to another bean-definition that is the
    65   * actual converter type to be instantiated.
    66   * <p>
    67   * When using Spring, a BeanPostProcessor class could also be defined that
    68   * intercepts creation of all Converter instances and automatically wraps
    69   * them in a SerializableConverter.
    70   *
    71   * <h2>Further details on serialization</h2>
    72   *
    73   * When using client-side state saving, the view tree is serialized <i>by JSF</i>
    74   * at the end of each request, and sent to the user along with the generated output.
    75   * <p>
    76   * When using server-side state, the JSF implementation might use JSF serialization
    77   * to generate data to cache in the session, or might just store a reference to
    78   * the unserialized component tree. The latter is not generally wise as switching
    79   * to client-side state saving later will suddenly change the way that the tree
    80   * is handled on save and postback and may expose bugs in component serialization
    81   * (particularly for custom components), so JSF implementations either default to
    82   * JSF-serialization even on server-side state, or do not even offer the option
    83   * to save an unserialized tree.
    84   * <p>
    85   * If a servlet engine is configured for distributed sessions, then when a request
    86   * is handled by a different server than handled the last request, the session is
    87   * serialized on the host that handled the old request and deserialized on the host
    88   * handling the new request.
    89   * <p>
    90   * Even without distributed sessions, a servlet engine will serialize sessions when
    91   * short of memory ("session passivation") and cache them on disk. Session serialization
    92   * also happens when a servlet engine is reconfigured for "hot restart", ie where the
    93   * server can be rebooted without losing user sessions.
    94   * <p>
    95   * With both the client-side or server-side with "normal" JSF serialization, JSF
    96   * will first try to serialize converters using the StateHolder methods, and only use
    97   * java.io.Serializable when that is not supported. Therefore having this class implement
    98   * StateHolder, and then requiring all converters used with this wrapper to implement
    99   * StateHolder solves the serialization issues. This class does not need to implement
    100  * java.io.Serializable because serialization is always done via the StateHolder methods
    101  * instead.
    102  * <p>
    103  * For applications where a raw JSF tree is stored in the session, then an attempt by
    104  * the server to serialize the session might trigger an attempt to use java.io.Serializable
    105  * apis on this object. As this does not implement the java.io.Serializable, an exception
    106  * will occur. This class cannot simply implement java.io.Serializable because the object
    107  * it references is usually proxied, and the proxies are not generally serialiuable. It
    108  * *might* be possible for this code to implement normal serialization by "unproxying"
    109  * the bean, invoking serialization on the real object, then on deserialize re-wrapping
    110  * the bean in proxies. However as this code is unlikely to ever be used, this has not
    111  * been implemented.
    112  * <p>
    113  * Hopefully in some later release, the Orchestra-generated proxies will be able to
    114  * correctly serialize themselves automatically. When that happens, this class will
    115  * no longer be needed.
    116  */
    117 public class SerializableConverter implements Converter, StateHolder
    118 {
    119     private static final long serialVersionUID = 2L;
    120
    121     private String beanName;
    122     private transient Converter converter;
    123     private transient Object[] converterState;
    124
    125     public SerializableConverter()
    126     {
    127         // setBeanName or restoreState must be called when this constructor is used
    128     }
    129
    130     public SerializableConverter(String beanName)
    131     {
    132         this.beanName = beanName;
    133     }
    134
    135     public SerializableConverter(String beanName, Converter instance)
    136     {
    137         this.beanName = beanName;
    138         this.converter = instance;
    139     }
    140
    141     public void setBeanName(String beanName)
    142     {
    143         this.beanName = beanName;
    144     }
    145
    146     protected Converter getConverter(FacesContext context)
    147     {
    148         if (this.converter == null)
    149         {
    150             Application application = context.getApplication();
    151             this.converter = (Converter) application.getVariableResolver().resolveVariable(context, beanName);
    152
    153             if (converterState != null)
    154             {
    155                 // see method restoreState
    156                 ((StateHolder) converter).restoreState(context, converterState);
    157
    158                 // state no longer needed
    159                 converterState = null;
    160             }
    161
    162         }
    163
    164         return this.converter;
    165     }
    166
    167     public Object getAsObject(FacesContext context, UIComponent component, String value) throws ConverterException
    168     {
    169         return getConverter(context).getAsObject(context, component, value);
    170     }
    171
    172     public String getAsString(FacesContext context, UIComponent component, Object value) throws ConverterException
    173     {
    174         return getConverter(context).getAsString(context, component, value);
    175     }
    176
    177     // StateHolder methods
    178
    179     public boolean isTransient()
    180     {
    181         return false;
    182     }
    183
    184     public void restoreState(FacesContext context, Object savedState)
    185     {
    186         Object[] state = (Object[]) savedState;
    187         beanName = (String) state[0];
    188
    189         if (state.length == 2)
    190         {
    191             // Ok, the converter must be a StateHolder...
    192             //
    193             // Ideally here we would just call getConverter() to obtain a converter
    194             // instance, then invoke its restoreState method immediately. However
    195             // there is a problem with that; in most cases the Converter instance
    196             // will be in view-controller scope, which means that the proxy created
    197             // for it needs to look up the bean that is the controller for the current
    198             // view and then map the bean into the same conversation as that bean.
    199             // Unfortunately looking up the controller for the current view requires
    200             // knowing the current viewId; that is available on the UIViewRoot object
    201             // - but the UIViewRoot doesn't exist yet as we are currently part-way
    202             // through the restore-view phase.
    203             //
    204             // The solution is ugly but effective: avoid calling getConverter here and
    205             // instead save the state data for later; on the first call to getConverter
    206             // do the state restoring then.
    207             converterState = (Object[]) state[1];
    208         }
    209     }
    210
    211     public Object saveState(FacesContext context)
    212     {
    213         Object[] state;
    214         Converter c = getConverter(context);
    215         if (c instanceof StateHolder)
    216         {
    217             state = new Object[2];
    218             state[1] = ((StateHolder) c).saveState(context);
    219         }
    220         else
    221         {
    222             state = new Object[1];
    223         }
    224         state[0] = beanName;
    225         return state;
    226     }
    227
    228     public void setTransient(boolean newTransientValue)
    229     {
    230     }
    231 }