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.util.event.servlet.ContextEventRepeater;
037: import edu.emory.mathcs.backport.java.util.concurrent.ScheduledThreadPoolExecutor;
038: import org.apache.commons.logging.Log;
039: import org.apache.commons.logging.LogFactory;
040:
041: import java.util.ArrayList;
042: import java.util.Collections;
043: import java.util.HashMap;
044: import java.util.Iterator;
045: import java.util.Map;
046:
047: /**
048: * The RenderManager is the central class for developers wanting to do
049: * server-initiated rendering. The recommended use is to have a single
050: * RenderManager for your application, typically configured as a
051: * application-scope, managed bean. Any class that needs to request server side
052: * render calls can do so using the RenderManager.
053: * <p/>
054: * Server-initiated renders can be requested directly via the {@link
055: * RenderManager#requestRender requestRender} method or you get a named {@link
056: * GroupAsyncRenderer} and add a Renderable implementation to request and
057: * receive render calls as part of a group.
058: * <p/>
059: * The RenderManager contains a reference to a single {@link RenderHub} that
060: * uses a special queue and thread pool to optimize render calls for reliability
061: * and scalability.
062: */
063: public class RenderManager implements Disposable {
064:
065: private static Log log = LogFactory.getLog(RenderManager.class);
066:
067: static final int MIN = 1;
068: public static final int ON_DEMAND = 2;
069: public static final int INTERVAL = 3;
070: public static final int DELAY = 4;
071: static final int MAX = 4;
072:
073: private RenderHub renderHub;
074: private Map groupMap;
075:
076: //The ContextEventRepeater stores listeners in a weakly referenced fashion
077: //so we need to hang on to a reference or it will quietly disappear.
078: private ContextDestroyedListener shutdownListener;
079:
080: /**
081: * No argument constructor suitable for using as a managed bean.
082: */
083: public RenderManager() {
084: shutdownListener = new ContextDestroyedListener(this );
085: ContextEventRepeater.addListener(shutdownListener);
086: groupMap = Collections.synchronizedMap(new HashMap());
087: renderHub = new RenderHub();
088: }
089:
090: /**
091: * Internal utility method that returns the proper type of {@link
092: * GroupAsyncRenderer} and ensures that is added to the managed collection,
093: * indexed by name.
094: *
095: * @param name the unique name of the GroupAsyncRenderer
096: * @param type the type of GroupAsyncRenderer
097: * @return the requested type of GroupAsyncRenderer
098: */
099: private synchronized AsyncRenderer getRenderer(String name, int type) {
100: if (name == null || name.trim().length() == 0) {
101: throw new IllegalArgumentException(
102: "illegal renderer name: " + name);
103: }
104:
105: if (type < MIN || type > MAX) {
106: throw new IllegalArgumentException(
107: "illegal renderer type: " + type);
108: }
109:
110: Object obj = groupMap.get(name);
111: if (obj != null) {
112: if (log.isTraceEnabled()) {
113: log.trace("existing renderer retrieved: " + name);
114: }
115: return (AsyncRenderer) obj;
116: }
117:
118: AsyncRenderer renderer = null;
119: switch (type) {
120: case ON_DEMAND:
121: renderer = new OnDemandRenderer();
122: break;
123: case INTERVAL:
124: renderer = new IntervalRenderer();
125: break;
126: case DELAY:
127: renderer = new DelayRenderer();
128: break;
129: }
130:
131: renderer.setName(name);
132: renderer.setRenderManager(this );
133: groupMap.put(name, renderer);
134:
135: if (log.isTraceEnabled()) {
136: log.trace("new renderer retrieved: " + name);
137: }
138:
139: return renderer;
140: }
141:
142: /**
143: * When an AsyncRenderer disposes itself, it also needs to remove itself
144: * from the RenderManager's collection.
145: *
146: * @param renderer The Renderer to remove
147: */
148: protected void removeRenderer(AsyncRenderer renderer) {
149: if (renderer == null) {
150: if (log.isInfoEnabled()) {
151: log.info("renderer is null");
152: }
153: return;
154: }
155:
156: Object removedRenderer = groupMap.remove(renderer.getName());
157: if (removedRenderer == null) {
158: if (log.isTraceEnabled()) {
159: log.trace("renderer " + renderer.getName()
160: + " not found");
161: }
162: } else {
163: if (log.isTraceEnabled()) {
164: log
165: .trace("renderer " + renderer.getName()
166: + " removed");
167: }
168: }
169: }
170:
171: /**
172: * Submits the supplied Renderable instance to the RenderHub for
173: * server-initiated render.
174: *
175: * @param renderable The {@link Renderable} instance to render.
176: */
177: public void requestRender(Renderable renderable) {
178: renderHub.requestRender(renderable);
179: }
180:
181: void relayRender(String rendererName) {
182: //no relay with the standard RenderManaager
183: }
184:
185: /**
186: * The OnDemandRenderer is a {@link GroupAsyncRenderer} that requests a
187: * server-initiated render of all the Renderables in its collection. The
188: * request is made immediately upon request.
189: * <p/>
190: * Thsi method returns the appropriate GroupAsyncRenderer based on the name.
191: * If the name is new, a new instance is created and stored in the
192: * RenderManager's collection. If the named GroupAsyncRenderer already
193: * exists, and it's of the proper type, then the existing instance is
194: * returned.
195: *
196: * @param name the name of the GroupAsyncRenderer
197: * @return a new or existing GroupAsyncRenderer, based on the name
198: */
199: public OnDemandRenderer getOnDemandRenderer(String name) {
200: return (OnDemandRenderer) getRenderer(name, ON_DEMAND);
201: }
202:
203: /**
204: * The IntervalRenderer is a {@link GroupAsyncRenderer} that requests a
205: * server-initiated render of all the Renderables in its collection. The
206: * request to render is made repeatedly at the set interval.
207: * <p/>
208: * Thsi method returns the appropriate GroupAsyncRenderer based on the name.
209: * If the name is new, a new instance is created and stored in the
210: * RenderManager's collection. If the named GroupAsyncRenderer already
211: * exists, and it's of the proper type, then the existing instance is
212: * returned.
213: *
214: * @param name the name of the GroupAsyncRenderer
215: * @return a new or existing GroupAsyncRenderer, based on the name
216: */
217: public IntervalRenderer getIntervalRenderer(String name) {
218: return (IntervalRenderer) getRenderer(name, INTERVAL);
219: }
220:
221: /**
222: * The DelayRenderer is a {@link GroupAsyncRenderer} that requests a
223: * server-initiated render of all the Renderables in its collection. The
224: * request to render is made once at a set point in the future.
225: * <p/>
226: * Thsi method returns the appropriate GroupAsyncRenderer based on the name.
227: * If the name is new, a new instance is created and stored in the
228: * RenderManager's collection. If the named GroupAsyncRenderer already
229: * exists, and it's of the proper type, then the existing instance is
230: * returned.
231: *
232: * @param name the name of the GroupAsyncRenderer
233: * @return a new or existing GroupAsyncRenderer, based on the name
234: */
235: public DelayRenderer getDelayRenderer(String name) {
236: return (DelayRenderer) getRenderer(name, DELAY);
237: }
238:
239: /**
240: * This method is used by {@link GroupAsyncRenderer}s that need to request
241: * render calls based on some sort of schedule. It uses a separate,
242: * configurable thread pool and queue than the core rendering service.
243: *
244: * @return the scheduled executor for this RenderManager
245: */
246: ScheduledThreadPoolExecutor getScheduledService() {
247: return renderHub.getScheduledService();
248: }
249:
250: /**
251: * This is typically called when the application is shutting down and we
252: * need to dispose of all the RenderManager's resources. It iterates
253: * through all of the named {@link GroupAsyncRenderer}s, calling dispose on
254: * each of them in turn. It then calls dispose on the {@link RenderHub}.
255: * Once the RenderManager has been disposed, it can no longer be used.
256: */
257: public void dispose() {
258: synchronized (groupMap) {
259:
260: //Bug 944
261: //We make a copy of the list of renderers to remove simply to
262: //iterate through them. This avoids a concurrent modification
263: //issue when each individual renderers dispose method attempts
264: //to remove itself from the official groupMap.
265: ArrayList renderList = new ArrayList(groupMap.size());
266: renderList.addAll(groupMap.values());
267: Iterator renderers = renderList.iterator();
268: while (renderers.hasNext()) {
269: AsyncRenderer renderer = (AsyncRenderer) renderers
270: .next();
271: renderer.dispose();
272: if (log.isTraceEnabled()) {
273: log.trace("renderer disposed: " + renderer);
274: }
275: }
276: groupMap.clear();
277: }
278: renderHub.dispose();
279: if (log.isDebugEnabled()) {
280: log.debug("all renderers and hub have been disposed");
281: }
282:
283: }
284:
285: /**
286: * Returns the named instance of an AsyncRenderer if it already exists
287: * otherwise it returns null.
288: *
289: * @param rendererName The name of the AsycRender to retrieve.
290: * @return An instance of an AsyncRenderer that is associated with the
291: * provided name.
292: */
293: public AsyncRenderer getRenderer(String rendererName) {
294: if (rendererName == null) {
295: return null;
296: }
297: return (AsyncRenderer) groupMap.get(rendererName);
298: }
299: }
|