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.webapp.xmlhttp;
035:
036: import com.icesoft.faces.context.BridgeFacesContext;
037: import com.icesoft.faces.context.ViewListener;
038: import com.icesoft.faces.webapp.http.common.Configuration;
039: import com.icesoft.faces.webapp.parser.ImplementationUtil;
040:
041: import edu.emory.mathcs.backport.java.util.concurrent.ExecutorService;
042: import edu.emory.mathcs.backport.java.util.concurrent.Executors;
043: import org.apache.commons.logging.Log;
044: import org.apache.commons.logging.LogFactory;
045:
046: import javax.faces.FactoryFinder;
047: import javax.faces.context.ExternalContext;
048: import javax.faces.context.FacesContext;
049: import javax.faces.lifecycle.Lifecycle;
050: import javax.faces.lifecycle.LifecycleFactory;
051: import java.io.Serializable;
052: import java.util.Collection;
053: import java.util.Map;
054:
055: /**
056: * The {@link PersistentFacesState} class allows an application to initiate
057: * rendering asynchronously and independently of user interaction.
058: * <p/>
059: * Typical use is to obtain a {@link PersistentFacesState} instance in a managed
060: * bean constructor and then use that instance for any relevant rendering
061: * requests.
062: * <p/>
063: * Applications should obtain the <code>PersistentFacesState</code> instance
064: * using the public static getInstance() method. The recommended approach is to
065: * call this method from a mangaged-bean constructor and use the instance
066: * obtained for any {@link #render} requests.
067: */
068: public class PersistentFacesState implements Serializable {
069: private static final Log log = LogFactory
070: .getLog(PersistentFacesState.class);
071: private static ExecutorService executorService = Executors
072: .newSingleThreadExecutor();
073: private static InheritableThreadLocal localInstance = new InheritableThreadLocal();
074: private BridgeFacesContext facesContext;
075: private Lifecycle lifecycle;
076:
077: private ClassLoader renderableClassLoader = null;
078: private boolean synchronousMode;
079: private Collection viewListeners;
080:
081: public PersistentFacesState(BridgeFacesContext facesContext,
082: Collection viewListeners, Configuration configuration) {
083: //JIRA case ICE-1365
084: //Save a reference to the web app classloader so that server-side
085: //render requests work regardless of how they are originated.
086: renderableClassLoader = Thread.currentThread()
087: .getContextClassLoader();
088:
089: this .facesContext = facesContext;
090: this .viewListeners = viewListeners;
091: this .synchronousMode = configuration.getAttributeAsBoolean(
092: "synchronousUpdate", false);
093: LifecycleFactory factory = (LifecycleFactory) FactoryFinder
094: .getFactory(FactoryFinder.LIFECYCLE_FACTORY);
095: this .lifecycle = factory
096: .getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE);
097: this .setCurrentInstance();
098: }
099:
100: public void setCurrentInstance() {
101: localInstance.set(this );
102: }
103:
104: /**
105: * Obtain the <code>PersistentFacesState</code> instance appropriate for the
106: * current context. This is managed through InheritableThreadLocal
107: * variables. The recommended approach is to call this method from a
108: * mangaged-bean constructor and use the instance obtained for any {@link
109: * #render} requests.
110: *
111: * @return the PersistentFacesState appropriate for the calling Thread
112: */
113: public static PersistentFacesState getInstance() {
114: return (PersistentFacesState) localInstance.get();
115: }
116:
117: /**
118: * Obtain the {@link PersistentFacesState} instance keyed by viewNumber from
119: * the specified sessionMap. This API is not intended for application use.
120: *
121: * @param sessionMap session-scope parameters
122: * @return the PersistentFacesState
123: * @deprecated
124: */
125: public static PersistentFacesState getInstance(Map sessionMap) {
126: return getInstance();
127: }
128:
129: /**
130: * Return the FacesContext associated with this instance.
131: *
132: * @return the FacesContext for this instance
133: */
134: public FacesContext getFacesContext() {
135: return facesContext;
136: }
137:
138: //todo: try to remove this method in the future
139: public void setFacesContext(BridgeFacesContext facesContext) {
140: this .facesContext = facesContext;
141: }
142:
143: /**
144: * Render the view associated with this <code>PersistentFacesState</code>.
145: * The user's browser will be immediately updated with any changes.
146: */
147: public void render() throws RenderingException {
148: warn();
149: facesContext.setCurrentInstance();
150: facesContext.setFocusId("");
151: synchronized (facesContext) {
152: try {
153: lifecycle.render(facesContext);
154: facesContext.release();
155: } catch (IllegalStateException e) {
156: if (log.isDebugEnabled()) {
157: log.debug("fatal render failure for viewNumber "
158: + facesContext.getViewNumber(), e);
159: }
160: throw new FatalRenderingException(
161: "fatal render failure for viewNumber "
162: + facesContext.getViewNumber(), e);
163: } catch (Exception e) {
164: if (log.isDebugEnabled()) {
165: log.debug(
166: "transient render failure for viewNumber "
167: + facesContext.getViewNumber(), e);
168: }
169: throw new TransientRenderingException(
170: "transient render failure for viewNumber "
171: + facesContext.getViewNumber(), e);
172: }
173: }
174: }
175:
176: /**
177: * Render the view associated with this <code>PersistentFacesState</code>.
178: * This takes place on a separate thread to guard against potential deadlock
179: * from calling {@link #render} during view rendering.
180: */
181: public void renderLater() {
182: warn();
183: executorService.execute(new RenderRunner());
184: }
185:
186: public void renderLater(long miliseconds) {
187: warn();
188: executorService.execute(new RenderRunner(miliseconds));
189: }
190:
191: /**
192: * Redirect browser to a different URI. The user's browser will be
193: * immediately redirected without any user interaction required.
194: *
195: * @param uri the relative or absolute URI.
196: */
197: public void redirectTo(String uri) {
198: warn();
199: try {
200: facesContext.setCurrentInstance();
201: ExternalContext externalContext = facesContext
202: .getExternalContext();
203: externalContext.redirect(uri);
204: } catch (Exception e) {
205: throw new RuntimeException(e);
206: }
207: }
208:
209: /**
210: * Redirect browser to a different page. The redirect page is selected based
211: * on the navigation rule. The user's browser will be immediately redirected
212: * without any user interaction required.
213: *
214: * @param outcome the 'from-outcome' field in the navigation rule.
215: */
216: public void navigateTo(String outcome) {
217: warn();
218: try {
219: facesContext.setCurrentInstance();
220: facesContext.getApplication().getNavigationHandler()
221: .handleNavigation(facesContext,
222: facesContext.getViewRoot().getViewId(),
223: outcome);
224: } catch (Exception e) {
225: throw new RuntimeException(e);
226: }
227: }
228:
229: /**
230: * Threads that used to server request/response cycles in an application
231: * server are generally pulled from a pool. Just before the thread is done
232: * completing the cycle, we should clear any local instance variables to
233: * ensure that they are not hanging on to any session references, otherwise
234: * the session and their resources are not released.
235: */
236: public void release() {
237: localInstance.set(null);
238: }
239:
240: /**
241: * Execute the view associated with this <code>PersistentFacesState</code>.
242: * This is typically followed immediatly by a call to
243: * {@link PersistentFacesState#render}.
244: */
245: public void execute() throws RenderingException {
246: facesContext.setCurrentInstance();
247: synchronized (facesContext) {
248: try {
249: if (ImplementationUtil.isJSF12()) {
250: //facesContext.renderResponse() skips phase listeners
251: //in JSF 1.2, so do a full execute with no stale input
252: //instead
253: facesContext.getExternalContext()
254: .getRequestParameterMap().clear();
255: } else {
256: facesContext.renderResponse();
257: }
258: lifecycle.execute(facesContext);
259: } catch (IllegalStateException e) {
260: if (log.isDebugEnabled()) {
261: log.debug("fatal render failure for viewNumber "
262: + facesContext.getViewNumber(), e);
263: }
264: throw new FatalRenderingException(
265: "fatal render failure for viewNumber "
266: + facesContext.getViewNumber(), e);
267: } catch (Exception e) {
268: if (log.isDebugEnabled()) {
269: log.debug(
270: "transient render failure for viewNumber "
271: + facesContext.getViewNumber(), e);
272: }
273: throw new TransientRenderingException(
274: "transient render failure for viewNumber "
275: + facesContext.getViewNumber(), e);
276: }
277: }
278: }
279:
280: public ClassLoader getRenderableClassLoader() {
281: return renderableClassLoader;
282: }
283:
284: public void addViewListener(ViewListener listener) {
285: viewListeners.add(listener);
286: }
287:
288: private class RenderRunner implements Runnable {
289: private long delay = 0;
290:
291: public RenderRunner() {
292: }
293:
294: public RenderRunner(long miliseconds) {
295: delay = miliseconds;
296: }
297:
298: /**
299: * <p>Not for application use. Entry point for {@link
300: * PersistentFacesState#renderLater}.</p>
301: */
302: public void run() {
303: try {
304: Thread.sleep(delay);
305: // JIRA #1377 Call execute before render.
306: execute();
307: render();
308: } catch (RenderingException e) {
309: if (log.isDebugEnabled()) {
310: log.debug("renderLater failed ", e);
311: }
312: } catch (InterruptedException e) {
313: //ignore
314: }
315: }
316: }
317:
318: private void warn() {
319: if (synchronousMode) {
320: log
321: .warn("Running in 'synchronous mode'. The page updates were queued but not sent.");
322: }
323: }
324: }
|