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 }