001: /*
002: * Version: MPL 1.1/GPL 2.0/LGPL 2.1
003: *
004: * "The contents of this file are subject to the Mozilla Public License
005: * Version 1.1 (the "License"); you may not use this file except in
006: * compliance with the License. You may obtain a copy of the License at
007: * http://www.mozilla.org/MPL/
008: *
009: * Software distributed under the License is distributed on an "AS IS"
010: * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
011: * License for the specific language governing rights and limitations under
012: * the License.
013: *
014: * The Original Code is ICEfaces 1.5 open source software code, released
015: * November 5, 2006. The Initial Developer of the Original Code is ICEsoft
016: * Technologies Canada, Corp. Portions created by ICEsoft are Copyright (C)
017: * 2004-2006 ICEsoft Technologies Canada, Corp. All Rights Reserved.
018: *
019: * Contributor(s): _____________________.
020: *
021: * Alternatively, the contents of this file may be used under the terms of
022: * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"
023: * License), in which case the provisions of the LGPL License are
024: * applicable instead of those above. If you wish to allow use of your
025: * version of this file only under the terms of the LGPL License and not to
026: * allow others to use your version of this file under the MPL, indicate
027: * your decision by deleting the provisions above and replace them with
028: * the notice and other provisions required by the LGPL License. If you do
029: * not delete the provisions above, a recipient may use your version of
030: * this file under either the MPL or the LGPL License."
031: *
032: */
033:
034: package com.icesoft.faces.async.render;
035:
036: import com.icesoft.faces.webapp.xmlhttp.FatalRenderingException;
037: import com.icesoft.faces.webapp.xmlhttp.PersistentFacesState;
038: import com.icesoft.faces.webapp.xmlhttp.RenderingException;
039: import com.icesoft.faces.webapp.xmlhttp.TransientRenderingException;
040: import com.icesoft.util.SeamUtilities;
041: import org.apache.commons.logging.Log;
042: import org.apache.commons.logging.LogFactory;
043:
044: import javax.faces.context.FacesContext;
045: import javax.servlet.http.HttpSession;
046: import javax.portlet.PortletSession;
047:
048: /**
049: * RunnableRender implements Runnable and is designed to wrap up a {@link
050: * Renderable} so that it can be used on the {@link RenderHub}s rendering
051: * queue.
052: *
053: * @author ICEsoft Technologies, Inc.
054: */
055: class RunnableRender implements Runnable {
056:
057: private static Log log = LogFactory.getLog(RenderHub.class);
058:
059: private Renderable renderable;
060:
061: public RunnableRender(Renderable renderable) {
062: this .renderable = renderable;
063: }
064:
065: public Renderable getRenderable() {
066: return renderable;
067: }
068:
069: /**
070: * Using the supplied {@link Renderable}, extract its {@link
071: * com.icesoft.faces.webapp.xmlhttp.PersistentFacesState
072: * PersistentFacesState} and call the {@link com.icesoft.faces.webapp.xmlhttp.PersistentFacesState#render
073: * PersistentFacesState.render} method.
074: * <p/>
075: * If a {@link com.icesoft.faces.webapp.xmlhttp.RenderingException
076: * RenderingException} occurs, it is caught and the {@link
077: * Renderable#renderingException} callback is called.
078: */
079: public void run() {
080: if (renderable == null) {
081: return;
082: }
083:
084: PersistentFacesState state = renderable.getState();
085:
086: //If the state is null, we can't render. It likely means that the
087: //application has not properly updated the current state reference
088: //to something meaningful as far as the RenderManager is concerned. This
089: //can be due to bean scoping or not updating the state in a "best
090: //practices" way (the constructor, a getter, etc.). It's not fatal but
091: //the application does need to ensure that the supplied state is valid
092: //so we throw a TransientRenderException back to the application's
093: //Renderable implementation.
094: if (state == null) {
095: String msg = "unable to render, PersistentFacesState is null";
096: if (log.isWarnEnabled()) {
097: log.warn(msg);
098: }
099: renderable
100: .renderingException(new TransientRenderingException(
101: msg));
102: return;
103: }
104:
105: // This in response to an application with ServerInitiatedRendering coupled
106: // with user interaction, and GET requests. If we don't update the thread
107: // local, once user action creates a new ViewRoot, the Render thread's
108: // version of state would forever be detached from the real view, resulting
109: // in no more updates
110: state.setCurrentInstance();
111:
112: //JIRA case ICE-1365
113: //Server-side render calls can potentially be called from threads
114: //that are outside the context of the web app which means that the
115: //context classloader for the newly created thread might be unable
116: //to access JSF artifacts. If the render is to succeed, the classloader
117: //that created the PersistentFacesState must be correct so we ensure
118: //that the context classloader of the new render thread is set
119: //accordingly. If the current security policy does not allow this then
120: //we have to hope that the appropriate class loader settings were
121: //transferred to this new thread. If not, then the security policy
122: //will need to be altered to allow this.
123: try {
124: Thread.currentThread().setContextClassLoader(
125: state.getRenderableClassLoader());
126: } catch (SecurityException se) {
127: if (log.isDebugEnabled()) {
128: log
129: .debug(
130: "setting context class loader is not permitted",
131: se);
132: }
133: }
134:
135: try {
136:
137: // If the user has logged out via some Seam Identity object, then we
138: // can't try to execute() the lifecycle, because the restoreView phase
139: // will throw an exception, and Seam's restoreView phase listener
140: // will catch it, meaning we're completely out of the loop.
141: // Instead, try to discover if the Session is valid at this point
142: // in time ourselves. Naturally, this isn't perfect, since we're not
143: // synchronized with user interaction.
144: if (SeamUtilities.isSeamEnvironment()) {
145: testSession(state);
146: }
147: state.execute();
148: state.render();
149:
150: } catch (IllegalStateException ise) {
151: renderable
152: .renderingException(new TransientRenderingException(
153: ise));
154:
155: } catch (RenderingException ex) {
156: renderable.renderingException(ex);
157: if (ex instanceof TransientRenderingException) {
158: if (log.isTraceEnabled()) {
159: log.trace("transient render exception", ex);
160: }
161: } else if (ex instanceof FatalRenderingException) {
162: if (log.isDebugEnabled()) {
163: log.debug("fatal render exception", ex);
164: }
165: } else {
166: if (log.isErrorEnabled()) {
167: log.error("unknown render exception", ex);
168: }
169: }
170: }
171: }
172:
173: /**
174: * See note above in run method. Just fiddle with the session to try to cause
175: * IllegalStateExceptions before Seam takes over.
176: *
177: * @param state PersistentFacesState used in rendering
178: * @throws IllegalStateException If logged out.
179: */
180: private void testSession(PersistentFacesState state)
181: throws IllegalStateException {
182: FacesContext fc = state.getFacesContext();
183: Object o = fc.getExternalContext().getSession(false);
184: if (o == null) {
185: renderable.renderingException(new FatalRenderingException(
186: "Session has ended (User Logout?)"));
187: } else {
188: if (o instanceof HttpSession) {
189: HttpSession session = (HttpSession) o;
190: session.getAttributeNames();
191: } else if (o instanceof PortletSession) {
192: PortletSession ps = (PortletSession) o;
193: ps.getAttributeNames();
194: }
195: }
196: }
197:
198: /**
199: * We override the equals method of Object so that we can compare
200: * RunnableRender instances against each other. Since the important pieces
201: * are "wrapped" up, we need to unwrap them to compare them correctly. For
202: * our purposes, we are really interested in whether the associated {@link
203: * com.icesoft.faces.webapp.xmlhttp.PersistentFacesState
204: * PersistentFacesState}s are equal so we "unwrap" each RunnableRender and
205: * compare the internal PersistentFacesStates.
206: *
207: * @param obj The RunnableRender to compare to.
208: * @return True if the internal PersistentFacesStates of each RunnableRender
209: * are equal. False otherwise.
210: */
211: public boolean equals(Object obj) {
212: if (obj == null || !(obj instanceof RunnableRender)
213: || renderable == null) {
214: return false;
215: }
216:
217: Renderable comparedRenderable = ((RunnableRender) obj)
218: .getRenderable();
219: if (comparedRenderable == null) {
220: return false;
221: }
222:
223: PersistentFacesState comparedState = comparedRenderable
224: .getState();
225: if (comparedState == null) {
226: return false;
227: }
228:
229: PersistentFacesState myState = renderable.getState();
230: if (myState == null) {
231: return false;
232: }
233:
234: return myState.equals(comparedState);
235: }
236: }
|