001: /* Copyright 2001 The JA-SIG Collaborative. All rights reserved.
002: * See license distributed with this file and
003: * available online at http://www.uportal.org/license.html
004: */
005:
006: package org.jasig.portal;
007:
008: import java.io.PrintWriter;
009: import java.io.StringWriter;
010: import java.util.Map;
011:
012: import javax.servlet.http.HttpServletRequest;
013: import javax.servlet.http.HttpServletResponse;
014:
015: import org.apache.commons.logging.Log;
016: import org.apache.commons.logging.LogFactory;
017: import org.jasig.portal.channels.support.IChannelTitle;
018: import org.jasig.portal.channels.support.IDynamicChannelTitleRenderer;
019: import org.jasig.portal.properties.PropertiesManager;
020: import org.jasig.portal.servlet.AttributeScopingRequestWrapper;
021: import org.jasig.portal.utils.SAX2BufferImpl;
022: import org.jasig.portal.utils.SetCheckInSemaphore;
023: import org.jasig.portal.utils.SoftHashMap;
024: import org.jasig.portal.utils.threading.BaseTask;
025: import org.xml.sax.ContentHandler;
026: import org.xml.sax.SAXException;
027:
028: import edu.emory.mathcs.backport.java.util.concurrent.CancellationException;
029: import edu.emory.mathcs.backport.java.util.concurrent.ExecutorService;
030: import edu.emory.mathcs.backport.java.util.concurrent.Future;
031: import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
032: import edu.emory.mathcs.backport.java.util.concurrent.TimeoutException;
033: import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicInteger;
034:
035: /**
036: * This class takes care of initiating channel rendering thread,
037: * monitoring it for timeouts, retreiving cache, and returning
038: * rendering results and status.
039: * @author Peter Kharchenko {@link <a href="mailto:pkharchenko@interactivebusiness.com"">pkharchenko@interactivebusiness.com"</a>}
040: * @version $Revision: 42516 $
041: */
042: public class ChannelRenderer implements IChannelRenderer,
043: IDynamicChannelTitleRenderer {
044:
045: protected final Log log = LogFactory.getLog(getClass());
046:
047: /**
048: * Default value for CACHE_CHANNELS.
049: * This value will be used when the corresponding property cannot be loaded.
050: */
051: private static final boolean DEFAULT_CACHE_CHANNELS = false;
052:
053: public static final boolean CACHE_CHANNELS = PropertiesManager
054: .getPropertyAsBoolean(
055: "org.jasig.portal.ChannelRenderer.cache_channels",
056: DEFAULT_CACHE_CHANNELS);
057:
058: public static final String[] renderingStatus = { "successful",
059: "failed", "timed out" };
060:
061: protected IChannel channel;
062: protected ChannelRuntimeData rd;
063: protected Map channelCache;
064: protected Map cacheTables;
065: protected PortalControlStructures pcs;
066:
067: protected boolean rendering;
068: protected boolean donerendering;
069:
070: protected Thread workerThread;
071:
072: protected Worker worker;
073: protected Future workTracker;
074:
075: protected long startTime;
076: protected long timeOut = java.lang.Long.MAX_VALUE;
077:
078: protected boolean ccacheable;
079:
080: protected static ExecutorService tp = null;
081: protected static Map systemCache = null;
082:
083: protected SetCheckInSemaphore groupSemaphore;
084: protected Object groupRenderingKey;
085:
086: /**
087: * Default contstructor
088: *
089: * @param chan an <code>IChannel</code> value
090: * @param runtimeData a <code>ChannelRuntimeData</code> value
091: * @param threadPool a <code>ThreadPool</code> value
092: */
093: public ChannelRenderer(IChannel chan,
094: ChannelRuntimeData runtimeData,
095: PortalControlStructures pcs, ExecutorService threadPool) {
096: this .channel = chan;
097: this .rd = runtimeData;
098: this .pcs = pcs;
099: this .rendering = false;
100: this .ccacheable = false;
101: tp = threadPool;
102:
103: if (systemCache == null) {
104: systemCache = ChannelManager.systemCache;
105: }
106:
107: this .groupSemaphore = null;
108: this .groupRenderingKey = null;
109: }
110:
111: /**
112: * Default contstructor
113: *
114: * @param chan an <code>IChannel</code> value
115: * @param runtimeData a <code>ChannelRuntimeData</code> value
116: * @param threadPool a <code>ThreadPool</code> value
117: * @param groupSemaphore a <code>SetCheckInSemaphore</code> for the current rendering group
118: * @param groupRenderingKey an <code>Object</code> to be used for check ins with the group semaphore
119: */
120: public ChannelRenderer(IChannel chan,
121: ChannelRuntimeData runtimeData,
122: PortalControlStructures pcs, ExecutorService threadPool,
123: SetCheckInSemaphore groupSemaphore, Object groupRenderingKey) {
124: this (chan, runtimeData, pcs, threadPool);
125: this .groupSemaphore = groupSemaphore;
126: this .groupRenderingKey = groupRenderingKey;
127: }
128:
129: /**
130: * Sets the channel on which ChannelRenderer is to operate.
131: *
132: * @param channel an <code>IChannel</code>
133: */
134: public void setChannel(IChannel channel) {
135: if (log.isDebugEnabled())
136: log
137: .debug("ChannelRenderer::setChannel() : channel is being reset!");
138: this .channel = channel;
139: if (this .worker != null) {
140: this .worker.setChannel(channel);
141: }
142: // clear channel cache
143: this .channelCache = null;
144: }
145:
146: /**
147: * Obtains a content cache specific for this channel instance.
148: *
149: * @return a key->rendering map for this channel
150: */
151: // XXX is this thread safe?
152: Map getChannelCache() {
153: if (this .channelCache == null) {
154: if ((this .channelCache = (SoftHashMap) this .cacheTables
155: .get(this .channel)) == null) {
156: this .channelCache = new SoftHashMap(1);
157: this .cacheTables.put(this .channel, this .channelCache);
158: }
159: }
160: return this .channelCache;
161: }
162:
163: /**
164: * Set the timeout value
165: * @param value timeout in milliseconds
166: */
167: public void setTimeout(long value) {
168: this .timeOut = value;
169: }
170:
171: public void setCacheTables(Map cacheTables) {
172: this .cacheTables = cacheTables;
173: }
174:
175: /**
176: * Informs IChannelRenderer that a character caching scheme
177: * will be used for the current rendering.
178: * @param setting a <code>boolean</code> value
179: */
180: public void setCharacterCacheable(boolean setting) {
181: this .ccacheable = setting;
182: }
183:
184: /**
185: * Start rendering of the channel in a new thread.
186: * Note that rendered information will be accumulated in a
187: * buffer until outputRendering() function is called.
188: * startRendering() is a non-blocking function.
189: */
190: public void startRendering() {
191: // start the rendering thread
192:
193: //Pass the Request/Response for this thread into the worker so it can use them in the pool thread
194: this .worker = new Worker(this .channel, this .rd, this .pcs
195: .getHttpServletRequest(), this .pcs
196: .getHttpServletResponse());
197:
198: this .workTracker = tp.submit(this .worker); // XXX is execute okay?
199: this .rendering = true;
200: this .startTime = System.currentTimeMillis();
201: }
202:
203: public void startRendering(SetCheckInSemaphore groupSemaphore,
204: Object groupRenderingKey) {
205: this .groupSemaphore = groupSemaphore;
206: this .groupRenderingKey = groupRenderingKey;
207: this .startRendering();
208: }
209:
210: /**
211: * <p>Cancels the rendering job.
212: **/
213: public void cancelRendering() {
214: if (null != this .workTracker) {
215: this .workTracker.cancel(true);
216: }
217: }
218:
219: /**
220: * Output channel rendering through a given ContentHandler.
221: * Note: call of outputRendering() without prior call to startRendering() is equivalent to
222: * sequential calling of startRendering() and then outputRendering().
223: * outputRendering() is a blocking function. It will return only when the channel completes rendering
224: * or fails to render by exceeding allowed rendering time.
225: * @param out Document Handler that will receive information rendered by the channel.
226: * @return error code. 0 - successful rendering; 1 - rendering failed; 2 - rendering timedOut;
227: */
228: public int outputRendering(ContentHandler out) throws Throwable {
229: int renderingStatus = completeRendering();
230: if (renderingStatus == RENDERING_SUCCESSFUL) {
231: SAX2BufferImpl buffer;
232: if ((buffer = this .worker.getBuffer()) != null) {
233: // unplug the buffer :)
234: try {
235: buffer.setAllHandlers(out);
236: buffer.outputBuffer();
237: return RENDERING_SUCCESSFUL;
238: } catch (SAXException e) {
239: // worst case scenario: partial content output :(
240: log
241: .error("ChannelRenderer::outputRendering() : following SAX exception occured : "
242: + e);
243: throw e;
244: }
245: } else {
246: log
247: .error("ChannelRenderer::outputRendering() : output buffer is null even though rendering was a success?! trying to rendering for ccaching ?");
248: throw new PortalException(
249: "unable to obtain rendering buffer");
250: }
251: }
252: return renderingStatus;
253: }
254:
255: /**
256: * Requests renderer to complete rendering and return status.
257: * This does exactly the same things as outputRendering except for the
258: * actual stream output.
259: *
260: * @return an <code>int</code> return status value
261: */
262:
263: public int completeRendering() throws Throwable {
264: if (!this .rendering) {
265: this .startRendering();
266: }
267: boolean abandoned = false;
268: long timeOutTarget = this .startTime + this .timeOut;
269:
270: // separate waits caused by rendering group
271: if (this .groupSemaphore != null) {
272: while (!this .worker.isSetRuntimeDataComplete()
273: && System.currentTimeMillis() < timeOutTarget
274: && !this .workTracker.isDone()) {
275: long wait = timeOutTarget - System.currentTimeMillis();
276: if (wait <= 0) {
277: wait = 1;
278: }
279: try {
280: synchronized (this .groupSemaphore) {
281: this .groupSemaphore.wait(wait);
282: }
283: } catch (InterruptedException ie) {
284: }
285: }
286: if (!this .worker.isSetRuntimeDataComplete()
287: && !this .workTracker.isDone()) {
288: this .workTracker.cancel(true);
289: abandoned = true;
290: if (log.isDebugEnabled())
291: log
292: .debug("ChannelRenderer::outputRendering() : killed. "
293: + "(key="
294: + this .groupRenderingKey.toString()
295: + ")");
296: } else {
297: this .groupSemaphore.waitOn();
298: }
299: // reset timer for rendering
300: timeOutTarget = System.currentTimeMillis() + this .timeOut;
301: }
302:
303: if (!abandoned) {
304: try {
305: this .workTracker.get(this .timeOut,
306: TimeUnit.MILLISECONDS);
307: } catch (TimeoutException te) {
308: if (log.isDebugEnabled()) {
309: log.debug(
310: "ChannelRenderer::outputRendering() : channel ["
311: + this .channel + "] timed out", te);
312: }
313: } catch (CancellationException ce) {
314:
315: if (log.isDebugEnabled()) {
316: Throwable t = null;
317: try {
318: // in a try block to ensure further errors don't block reporting
319: // the CancellationException.
320: t = this .worker.getException();
321: } catch (Exception e) {
322: // ignore problem in getting the exception to report.
323: }
324: log
325: .debug("ChannelRenderer::outputRendering() : channel ["
326: + this .channel
327: + "] threw an exception ["
328: + t
329: + "] and so its task was cancelled.");
330: }
331:
332: } catch (Exception e) {
333: // no matter what went wrong (CancellationException, a NullPointerException, etc.)
334: // the recovery code following this attempt to get the result from the workTracker Future
335: // should be allowed to run.
336: log
337: .error(
338: "Unexpected exceptional condition trying to get the result from the workTracker Future rendering channel ["
339: + this .channel + "].", e);
340: }
341:
342: if (!this .workTracker.isDone()) {
343: this .workTracker.cancel(true);
344: abandoned = true;
345: if (log.isDebugEnabled())
346: log
347: .debug("ChannelRenderer::outputRendering() : killed.");
348: } else {
349: boolean successful = this .workTracker.isDone()
350: && !this .workTracker.isCancelled()
351: && this .worker.getException() == null;
352: abandoned = !successful;
353: }
354:
355: }
356:
357: if (!abandoned && this .worker.done()) {
358: if (this .worker.successful()
359: && (((this .worker.getBuffer()) != null) || (this .ccacheable && this .worker.cbuffer != null))) {
360: return RENDERING_SUCCESSFUL;
361:
362: } else {
363: // rendering was not successful
364: Throwable e;
365: if ((e = this .worker.getException()) != null)
366: throw new InternalPortalException(e);
367: // should never get there, unless thread.stop() has seriously messed things up for the worker thread.
368: return RENDERING_FAILED;
369: }
370: } else {
371: Throwable e = null;
372: if (this .worker != null) {
373: e = this .worker.getException();
374: }
375:
376: if (e != null) {
377: throw new InternalPortalException(e);
378: } else {
379: // Assume rendering has timed out
380: return RENDERING_TIMED_OUT;
381: }
382: }
383: }
384:
385: /**
386: * Returns rendered buffer.
387: * This method does not perform any status checks, so make sure to call completeRendering() prior to invoking this method.
388: *
389: * @return rendered buffer
390: */
391: public SAX2BufferImpl getBuffer() {
392: return this .worker != null ? this .worker.getBuffer() : null;
393: }
394:
395: /**
396: * Returns a character output of a channel rendering.
397: */
398: public String getCharacters() {
399: if (this .worker != null) {
400: return this .worker.getCharacters();
401: }
402:
403: if (log.isDebugEnabled()) {
404: log
405: .debug("ChannelRenderer::getCharacters() : worker is null already !");
406: }
407:
408: return null;
409: }
410:
411: /**
412: * Sets a character cache for the current rendering.
413: */
414: public void setCharacterCache(String chars) {
415: if (this .worker != null) {
416: this .worker.setCharacterCache(chars);
417: }
418: }
419:
420: /**
421: * This method suppose to take care of the runaway rendering threads.
422: * This method will be called from ChannelManager explictly.
423: */
424: protected void kill() {
425: if (this .workTracker != null && !this .workTracker.isDone())
426: this .workTracker.cancel(true);
427: }
428:
429: public String toString() {
430: StringBuffer sb = new StringBuffer();
431:
432: sb.append("ChannelRenderer ");
433: sb.append("channel = [").append(this .channel).append("] ");
434: sb.append("rd = [").append(this .rd).append("] ");
435: sb.append("rendering=").append(this .rendering).append(" ");
436: sb.append("donerendering=").append(this .donerendering).append(
437: " ");
438: sb.append("startTime=").append(this .startTime).append(" ");
439: sb.append("timeOut=").append(this .timeOut).append(" ");
440:
441: return sb.toString();
442: }
443:
444: public String getChannelTitle() {
445:
446: if (log.isTraceEnabled()) {
447: log.trace("Getting channel title for ChannelRenderer "
448: + this );
449: }
450:
451: // default to null, which indicates the ChannelRenderer doesn't have
452: // a dynamic channel title available.
453: String channelTitle = null;
454: try {
455: // block on channel rendering to allow channel opportunity to
456: // provide dynamic title.
457: int renderingStatus = completeRendering();
458: if (renderingStatus == RENDERING_SUCCESSFUL) {
459: channelTitle = this .worker.getChannelTitle();
460: }
461: } catch (Throwable t) {
462: log.error(
463: "Channel rendering failed while getting title for channel renderer "
464: + this , t);
465: }
466:
467: // will be null indicating no dynamic title unless successfully obtained title.
468: return channelTitle;
469:
470: }
471:
472: protected class Worker extends BaseTask {
473: private boolean successful;
474: private boolean done;
475: private boolean setRuntimeDataComplete;
476: private IChannel channel;
477: private ChannelRuntimeData rd;
478: private SAX2BufferImpl buffer;
479: private String cbuffer;
480: private HttpServletRequest req;
481: private HttpServletResponse res;
482:
483: /**
484: * The dynamic title of the channel, if any. Null otherwise.
485: */
486: private String channelTitle = null;
487:
488: //Pass the request/response into the worker, this will allow the objects
489: //to be passed into the ThreadLocalized PortalControlStructures when execute()
490: //is called by the worker thread.
491: public Worker(IChannel ch, ChannelRuntimeData runtimeData,
492: HttpServletRequest req, HttpServletResponse res) {
493: this .channel = ch;
494: this .rd = runtimeData;
495: successful = false;
496: done = false;
497: setRuntimeDataComplete = false;
498: buffer = null;
499: cbuffer = null;
500: this .req = new AttributeScopingRequestWrapper(req);
501: this .res = res;
502: }
503:
504: public void setChannel(IChannel ch) {
505: this .channel = ch;
506: }
507:
508: public boolean isSetRuntimeDataComplete() {
509: return this .setRuntimeDataComplete;
510: }
511:
512: public void execute() throws Exception {
513: try {
514: if (pcs != null) {
515: //Set the request/response for this thread.
516: pcs.setHttpServletRequest(this .req);
517: pcs.setHttpServletResponse(this .res);
518:
519: if (channel instanceof IPrivileged) {
520: ((IPrivileged) channel)
521: .setPortalControlStructures(pcs);
522: }
523: }
524:
525: if (rd != null) {
526: channel.setRuntimeData(rd);
527: }
528: setRuntimeDataComplete = true;
529:
530: if (groupSemaphore != null) {
531: groupSemaphore.checkInAndWaitOn(groupRenderingKey);
532: }
533:
534: if (CACHE_CHANNELS) {
535: // try to obtain rendering from cache
536: if (channel instanceof ICacheable) {
537: ChannelCacheKey key = ((ICacheable) channel)
538: .generateKey();
539: if (key != null) {
540: if (key.getKeyScope() == ChannelCacheKey.SYSTEM_KEY_SCOPE) {
541: ChannelCacheEntry entry = (ChannelCacheEntry) systemCache
542: .get(key.getKey());
543: if (entry != null) {
544: // found cached page
545: // check page validity
546: if (((ICacheable) channel)
547: .isCacheValid(entry.validity)
548: && (entry.buffer != null)) {
549: // use it
550: if (ccacheable
551: && (entry.buffer instanceof String)) {
552: cbuffer = (String) entry.buffer;
553: if (log.isDebugEnabled()) {
554: log
555: .debug("ChannelRenderer.Worker::run() : retrieved system-wide cached character content based on a key \""
556: + key
557: .getKey()
558: + "\"");
559: }
560: } else if (entry.buffer instanceof SAX2BufferImpl) {
561: buffer = (SAX2BufferImpl) entry.buffer;
562: if (log.isDebugEnabled()) {
563: log
564: .debug("ChannelRenderer.Worker::run() : retrieved system-wide cached content based on a key \""
565: + key
566: .getKey()
567: + "\"");
568: }
569: }
570: } else {
571: // remove it
572: systemCache
573: .remove(key.getKey());
574: if (log.isDebugEnabled()) {
575: log
576: .debug("ChannelRenderer.Worker::run() : removed system-wide unvalidated cache based on a key \""
577: + key
578: .getKey()
579: + "\"");
580: }
581: }
582: }
583: } else {
584: // by default we assume INSTANCE_KEY_SCOPE
585: ChannelCacheEntry entry = (ChannelCacheEntry) getChannelCache()
586: .get(key.getKey());
587: if (entry != null) {
588: // found cached page
589: // check page validity
590: if (((ICacheable) channel)
591: .isCacheValid(entry.validity)
592: && (entry.buffer != null)) {
593: // use it
594: if (ccacheable
595: && (entry.buffer instanceof String)) {
596: cbuffer = (String) entry.buffer;
597: if (log.isDebugEnabled()) {
598: log
599: .debug("ChannelRenderer.Worker::run() : retrieved instance-cached character content based on a key \""
600: + key
601: .getKey()
602: + "\"");
603: }
604:
605: } else if (entry.buffer instanceof SAX2BufferImpl) {
606: buffer = (SAX2BufferImpl) entry.buffer;
607: if (log.isDebugEnabled()) {
608: log
609: .debug("ChannelRenderer.Worker::run() : retrieved instance-cached content based on a key \""
610: + key
611: .getKey()
612: + "\"");
613: }
614: }
615: } else {
616: // remove it
617: getChannelCache().remove(
618: key.getKey());
619: if (log.isDebugEnabled()) {
620: log
621: .debug("ChannelRenderer.Worker::run() : removed unvalidated instance-cache based on a key \""
622: + key
623: .getKey()
624: + "\"");
625: }
626: }
627: }
628: }
629: }
630:
631: // future work: here we should synchronize based on a particular cache key.
632: // Imagine a VERY popular cache entry timing out, then portal will attempt
633: // to re-render the page in many threads (serving many requests) simultaneously.
634: // If one was to synchronize on writing cache for a particular key, one thread
635: // would render and others would wait for it to complete.
636:
637: // check if need to render
638: if ((ccacheable && cbuffer == null && buffer == null)
639: || ((!ccacheable) && buffer == null)) {
640: if (ccacheable
641: && channel instanceof ICharacterChannel) {
642: StringWriter sw = new StringWriter(100);
643: PrintWriter pw = new PrintWriter(sw);
644: ((ICharacterChannel) channel)
645: .renderCharacters(pw);
646: pw.flush();
647: cbuffer = sw.toString();
648: // save cache
649: if (key != null) {
650: if (key.getKeyScope() == ChannelCacheKey.SYSTEM_KEY_SCOPE) {
651: systemCache
652: .put(
653: key.getKey(),
654: new ChannelCacheEntry(
655: cbuffer,
656: key
657: .getKeyValidity()));
658: if (log.isDebugEnabled()) {
659: log
660: .debug("ChannelRenderer.Worker::run() : recorded system character cache based on a key \""
661: + key
662: .getKey()
663: + "\"");
664: }
665: } else {
666: getChannelCache()
667: .put(
668: key.getKey(),
669: new ChannelCacheEntry(
670: cbuffer,
671: key
672: .getKeyValidity()));
673: if (log.isDebugEnabled()) {
674: log
675: .debug("ChannelRenderer.Worker::run() : recorded instance character cache based on a key \""
676: + key
677: .getKey()
678: + "\"");
679: }
680: }
681: }
682: } else {
683: // need to render again and cache the output
684: buffer = new SAX2BufferImpl();
685: buffer.startBuffering();
686: channel.renderXML(buffer);
687:
688: // save cache
689: if (key != null) {
690:
691: if (key.getKeyScope() == ChannelCacheKey.SYSTEM_KEY_SCOPE) {
692: systemCache
693: .put(
694: key.getKey(),
695: new ChannelCacheEntry(
696: buffer,
697: key
698: .getKeyValidity()));
699: if (log.isDebugEnabled()) {
700: log
701: .debug("ChannelRenderer.Worker::run() : recorded system cache based on a key \""
702: + key
703: .getKey()
704: + "\"");
705: }
706: } else {
707: getChannelCache()
708: .put(
709: key.getKey(),
710: new ChannelCacheEntry(
711: buffer,
712: key
713: .getKeyValidity()));
714: if (log.isDebugEnabled()) {
715: log
716: .debug("ChannelRenderer.Worker::run() : recorded instance cache based on a key \""
717: + key
718: .getKey()
719: + "\"");
720: }
721: }
722: }
723: }
724: }
725: } else {
726: if (ccacheable
727: && channel instanceof ICharacterChannel) {
728: StringWriter sw = new StringWriter(100);
729: PrintWriter pw = new PrintWriter(sw);
730: ((ICharacterChannel) channel)
731: .renderCharacters(pw);
732: pw.flush();
733: cbuffer = sw.toString();
734: } else {
735: buffer = new SAX2BufferImpl();
736: buffer.startBuffering();
737: channel.renderXML(buffer);
738: }
739: }
740: } else {
741: // in the case when channel cache is not enabled
742: buffer = new SAX2BufferImpl();
743: buffer.startBuffering();
744: channel.renderXML(buffer);
745: }
746: successful = true;
747: } catch (Exception e) {
748: if (groupSemaphore != null) {
749: groupSemaphore.checkIn(groupRenderingKey);
750: }
751: this .setException(e);
752: } finally {
753: if (pcs != null) {
754: //Clear the request/response for this thread
755: pcs.setHttpServletRequest(null);
756: pcs.setHttpServletResponse(null);
757: }
758: }
759:
760: /*
761: * Get the channel's ChannelRuntimeProperties, and handle them.
762: */
763: processChannelRuntimeProperties();
764:
765: done = true;
766: }
767:
768: /**
769: * Query the channel for ChannelRuntimePRoperties and process those
770: * properties.
771: *
772: * Currently, only handles the optional {@link IChannelTitle} interface.
773: */
774: private void processChannelRuntimeProperties() {
775: ChannelRuntimeProperties channelProps = this .channel
776: .getRuntimeProperties();
777:
778: if (channelProps != null) {
779: if (channelProps instanceof IChannelTitle) {
780:
781: this .channelTitle = ((IChannelTitle) channelProps)
782: .getChannelTitle();
783: if (log.isTraceEnabled()) {
784: log.trace("Read title " + this .channelTitle
785: + ".");
786: }
787: } else {
788: if (log.isTraceEnabled()) {
789: log
790: .trace("ChannelRuntimeProperties were non-null but did not implement ITitleable.");
791: }
792: }
793: } else {
794: if (log.isTraceEnabled()) {
795: log
796: .trace("ChannelRuntimeProperties were null from channel "
797: + channel);
798: }
799: }
800: }
801:
802: public boolean successful() {
803: return this .successful;
804: }
805:
806: public SAX2BufferImpl getBuffer() {
807: return this .buffer;
808: }
809:
810: /**
811: * Returns a character output of a channel rendering.
812: */
813: public String getCharacters() {
814: if (ccacheable) {
815: return this .cbuffer;
816: } else {
817: log
818: .error("ChannelRenderer.Worker::getCharacters() : attempting to obtain character data while character caching is not enabled !");
819: return null;
820: }
821: }
822:
823: /**
824: * Sets a character cache for the current rendering.
825: */
826: public void setCharacterCache(String chars) {
827: cbuffer = chars;
828: if (CACHE_CHANNELS) {
829: // try to obtain rendering from cache
830: if (channel instanceof ICacheable) {
831: ChannelCacheKey key = ((ICacheable) channel)
832: .generateKey();
833: if (key != null) {
834: if (log.isDebugEnabled()) {
835: log
836: .debug("ChannelRenderer::setCharacterCache() : called on a key \""
837: + key.getKey() + "\"");
838: }
839: ChannelCacheEntry entry = null;
840: if (key.getKeyScope() == ChannelCacheKey.SYSTEM_KEY_SCOPE) {
841: entry = (ChannelCacheEntry) systemCache
842: .get(key.getKey());
843: if (entry == null) {
844: if (log.isDebugEnabled()) {
845: log
846: .debug("ChannelRenderer::setCharacterCache() : setting character cache buffer based on a system key \""
847: + key.getKey()
848: + "\"");
849: }
850: entry = new ChannelCacheEntry(chars,
851: key.getKeyValidity());
852: } else {
853: entry.buffer = chars;
854: }
855: systemCache.put(key.getKey(), entry);
856: } else {
857: // by default we assume INSTANCE_KEY_SCOPE
858: entry = (ChannelCacheEntry) getChannelCache()
859: .get(key.getKey());
860: if (entry == null) {
861: if (log.isDebugEnabled()) {
862: log
863: .debug("ChannelRenderer::setCharacterCache() : no existing cache on a key \""
864: + key.getKey()
865: + "\"");
866: }
867: entry = new ChannelCacheEntry(chars,
868: key.getKeyValidity());
869: } else {
870: entry.buffer = chars;
871: }
872: getChannelCache().put(key.getKey(), entry);
873: }
874: } else {
875: if (log.isDebugEnabled()) {
876: log
877: .debug("ChannelRenderer::setCharacterCache() : channel cache key is null.");
878: }
879: }
880: }
881: }
882: }
883:
884: public boolean done() {
885: return this .done;
886: }
887:
888: /**
889: * Get a Sring representing the dynamic channel title reported by the
890: * IChannel this ChannelRenderer rendered. Returns null if the channel
891: * reported no such title or the channel isn't done rendering.
892: *
893: * @return dynamic channel title, or null if no title available.
894: */
895: String getChannelTitle() {
896:
897: if (log.isTraceEnabled()) {
898: log.trace("Getting channel title (" + this .channelTitle
899: + "] for " + this );
900: }
901:
902: // currently, just provides no dynamic title if not done rendering
903: if (this.done) {
904: return this.channelTitle;
905: } else {
906: return null;
907: }
908: }
909: }
910:
911: }
|