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 }