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 }