Package org.jasig.portal

Source Code of org.jasig.portal.ChannelRenderer$Worker

/* Copyright 2001 The JA-SIG Collaborative.  All rights reserved.
*  See license distributed with this file and
*  available online at http://www.uportal.org/license.html
*/

package org.jasig.portal;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Map;

import org.jasig.portal.channels.support.IChannelTitle;
import org.jasig.portal.channels.support.IDynamicChannelTitleRenderer;
import org.jasig.portal.properties.PropertiesManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasig.portal.utils.SAX2BufferImpl;
import org.jasig.portal.utils.SetCheckInSemaphore;
import org.jasig.portal.utils.SoftHashMap;
import org.jasig.portal.utils.threading.BaseTask;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;

import edu.emory.mathcs.backport.java.util.concurrent.ExecutorService;
import edu.emory.mathcs.backport.java.util.concurrent.Future;
import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
import edu.emory.mathcs.backport.java.util.concurrent.TimeoutException;

/**
* This class takes care of initiating channel rendering thread,
* monitoring it for timeouts, retreiving cache, and returning
* rendering results and status.
* @author <a href="mailto:pkharchenko@interactivebusiness.com">Peter Kharchenko</a>
* @version $Revision: 1.45.2.6 $
*/
public class ChannelRenderer
    implements IChannelRenderer, IDynamicChannelTitleRenderer
{
    protected final Log log = LogFactory.getLog(getClass());
   
    /**
     * Default value for CACHE_CHANNELS.
     * This value will be used when the corresponding property cannot be loaded.
     */
    private static final boolean DEFAULT_CACHE_CHANNELS = false;
   
    public static final boolean CACHE_CHANNELS=PropertiesManager.getPropertyAsBoolean("org.jasig.portal.ChannelRenderer.cache_channels", DEFAULT_CACHE_CHANNELS);
 
    public static final String[] renderingStatus={"successful","failed","timed out"};

    protected IChannel channel;
    protected ChannelRuntimeData rd;
    protected Map channelCache;
    protected Map cacheTables;

    protected boolean rendering;
    protected boolean donerendering;

    protected Thread workerThread;

    protected Worker worker;
    protected Future workTracker;

    protected long startTime;
    protected long timeOut = java.lang.Long.MAX_VALUE;

    protected boolean ccacheable;

    protected static ExecutorService tp=null;
    protected static Map systemCache=null;

    protected SetCheckInSemaphore groupSemaphore;
    protected Object groupRenderingKey;

    /**
     * Default contstructor
     *
     * @param chan an <code>IChannel</code> value
     * @param runtimeData a <code>ChannelRuntimeData</code> value
     * @param threadPool a <code>ThreadPool</code> value
     */
    public ChannelRenderer (IChannel chan,ChannelRuntimeData runtimeData, ExecutorService threadPool) {
        this.channel=chan;
        this.rd=runtimeData;
        this.rendering = false;
        this.ccacheable=false;
        tp = threadPool;

        if(systemCache==null) {
            systemCache=ChannelManager.systemCache;
        }

        this.groupSemaphore=null;
        this.groupRenderingKey=null;
    }


    /**
     * Default contstructor
     *
     * @param chan an <code>IChannel</code> value
     * @param runtimeData a <code>ChannelRuntimeData</code> value
     * @param threadPool a <code>ThreadPool</code> value
     * @param groupSemaphore a <code>SetCheckInSemaphore</code> for the current rendering group
     * @param groupRenderingKey an <code>Object</code> to be used for check ins with the group semaphore
     */
    public ChannelRenderer (IChannel chan,ChannelRuntimeData runtimeData, ExecutorService threadPool, SetCheckInSemaphore groupSemaphore, Object groupRenderingKey) {
        this(chan,runtimeData,threadPool);
        this.groupSemaphore=groupSemaphore;
        this.groupRenderingKey=groupRenderingKey;
    }


    /**
     * Sets the channel on which ChannelRenderer is to operate.
     *
     * @param channel an <code>IChannel</code>
     */
    public void setChannel(IChannel channel) {
        if (log.isDebugEnabled()) {
            log.debug("ChannelRenderer::setChannel() : channel is being reset!");
        }
        this.channel=channel;
        if(this.worker!=null) {
            this.worker.setChannel(channel);
        }
        // clear channel chace
        this.channelCache=null;
    }
   
    /**
     * Obtains a content cache specific for this channel instance.
     *
     * @return a key->rendering map for this channel
     */
    // XXX is this thread safe?
    Map getChannelCache() {
        if(this.channelCache==null) {
            if((this.channelCache=(SoftHashMap)this.cacheTables.get(this.channel))==null) {
                this.channelCache=new SoftHashMap(1);
                this.cacheTables.put(this.channel,this.channelCache);
            }
        }
        return this.channelCache;
    }


    /**
     * Set the timeout value
     * @param value timeout in milliseconds
     */
    public void setTimeout (long value) {
        this.timeOut = value;
    }

    public void setCacheTables(Map cacheTables) {
        this.cacheTables=cacheTables;
    }

    /**
     * Informs IChannelRenderer that a character caching scheme
     * will be used for the current rendering.
     * @param setting a <code>boolean</code> value
     */
    public void setCharacterCacheable(boolean setting) {
        this.ccacheable=setting;
    }

  /**
   * Start rendering of the channel in a new thread.
   * Note that rendered information will be accumulated in a
   * buffer until outputRendering() function is called.
   * startRendering() is a non-blocking function.
   */
  public void startRendering ()
  {
    // start the rendering thread

    this.worker = new Worker (this.channel,this.rd);

    this.workTracker = tp.submit(this.worker); // XXX is execute okay?
    this.rendering = true;
    this.startTime = System.currentTimeMillis ();
  }

    public void startRendering(SetCheckInSemaphore groupSemaphore, Object groupRenderingKey) {
        this.groupSemaphore=groupSemaphore;
        this.groupRenderingKey=groupRenderingKey;
        this.startRendering();
    }

    /**
     * <p>Cancels the rendering job.
     **/
    public void cancelRendering()
    {
        if (null != this.workTracker) {
            this.workTracker.cancel(true);
        }
    }

  /**
   * Output channel rendering through a given ContentHandler.
   * Note: call of outputRendering() without prior call to startRendering() is equivalent to
   * sequential calling of startRendering() and then outputRendering().
   * outputRendering() is a blocking function. It will return only when the channel completes rendering
   * or fails to render by exceeding allowed rendering time.
   * @param out Document Handler that will receive information rendered by the channel.
   * @return error code. 0 - successful rendering; 1 - rendering failed; 2 - rendering timedOut;
   */
    public int outputRendering (ContentHandler out) throws Throwable {
        int renderingStatus=completeRendering();
        if(renderingStatus==RENDERING_SUCCESSFUL) {
            SAX2BufferImpl buffer;
            if ((buffer=this.worker.getBuffer())!=null) {
                // unplug the buffer :)
                try {
                    buffer.setAllHandlers(out);
                    buffer.outputBuffer();
                    return RENDERING_SUCCESSFUL;
                } catch (SAXException e) {
                    // worst case scenario: partial content output :(
                    log.error( "ChannelRenderer::outputRendering() : following SAX exception occured : "+e, e);
                    throw e;
                }
            } else {
                log.error( "ChannelRenderer::outputRendering() : output buffer is null even though rendering was a success?! trying to rendering for ccaching ?");
                throw new PortalException("unable to obtain rendering buffer");
            }
        }
        return renderingStatus;
    }


    /**
     * Requests renderer to complete rendering and return status.
     * This does exactly the same things as outputRendering except for the
     * actual stream output.
     *
     * @return an <code>int</code> return status value
     */

    public int completeRendering() throws Throwable {
        if (!this.rendering) {
            this.startRendering ();
        }
        boolean abandoned=false;
        long timeOutTarget = this.startTime + this.timeOut;
     
     
        // separate waits caused by rendering group
        if(this.groupSemaphore!=null) {
            while(!this.worker.isSetRuntimeDataComplete() && System.currentTimeMillis() < timeOutTarget && !this.workTracker.isDone()) {
                long wait=timeOutTarget-System.currentTimeMillis();
                if(wait<=0) { wait=1; }
                try {
                    synchronized(this.groupSemaphore) {
                        this.groupSemaphore.wait(wait);
                    }
                } catch (InterruptedException ie) {}
            }
            if(!this.worker.isSetRuntimeDataComplete() && !this.workTracker.isDone()) {
                this.workTracker.cancel(true);
                abandoned=true;
                if (log.isDebugEnabled()) {
                    log.debug("ChannelRenderer::outputRendering() : killed. " +
                            "(key="+this.groupRenderingKey.toString()+")");
                }
            } else {
                this.groupSemaphore.waitOn();
            }
            // reset timer for rendering
            timeOutTarget=System.currentTimeMillis()+this.timeOut;
        }
     
        if(!abandoned) {
            try {
                this.workTracker.get(this.timeOut, TimeUnit.MILLISECONDS);
            } catch (TimeoutException te) {
                if (log.isDebugEnabled()) {
                    log.debug("ChannelRenderer::outputRendering() : timed out", te);
                }
               
            }
         
            if(!this.workTracker.isDone()) {
                this.workTracker.cancel(true);
                abandoned=true;
                if (log.isDebugEnabled())
                    log.debug("ChannelRenderer::outputRendering() : killed.");
            } else {
                boolean successful = this.workTracker.isDone() && !this.workTracker.isCancelled() && this.worker.getException() == null;
                abandoned=!successful;
            }
         
        }
     
        if (!abandoned && this.worker.done ()) {
            if (this.worker.successful() && (((this.worker.getBuffer())!=null) || (this.ccacheable && this.worker.cbuffer!=null))) {
                return RENDERING_SUCCESSFUL;

            } else {
                // rendering was not successful
                Throwable e;
                if((e=this.worker.getException())!=null) throw new InternalPortalException(e);
                // should never get there, unless thread.stop() has seriously messed things up for the worker thread.
                return RENDERING_FAILED;
            }
        } else {
            Throwable e = null;
            if (this.worker != null) {
              e = this.worker.getException();
            }
           
            if (e != null) {
                throw new InternalPortalException(e);
            } else {
                // Assume rendering has timed out
                return RENDERING_TIMED_OUT;
            }
        }
    }


    /**
     * Returns rendered buffer.
     * This method does not perform any status checks, so make sure to call completeRendering() prior to invoking this method.
     *
     * @return rendered buffer
     */
    public SAX2BufferImpl getBuffer() {
        return this.worker != null ? this.worker.getBuffer() : null;
    }

    /**
     * Returns a character output of a channel rendering.
     */
    public String getCharacters() {
        if(this.worker!=null) {
            return this.worker.getCharacters();
        }
       
        if (log.isDebugEnabled()) {
            log.debug("ChannelRenderer::getCharacters() : worker is null already !");
        }

        return null;
    }


    /**
     * Sets a character cache for the current rendering.
     */
    public void setCharacterCache(String chars) {
        if(this.worker!=null) {
            this.worker.setCharacterCache(chars);
        }
    }

    /**
     * This method suppose to take care of the runaway rendering threads.
     * This method will be called from ChannelManager explictly.
     */
    protected void kill() {
        if(this.workTracker!=null && !this.workTracker.isDone())
            this.workTracker.cancel(true);
    }

    public String toString() {
        StringBuffer sb = new StringBuffer();
       
        sb.append("ChannelRenderer ");
        sb.append("channel = [").append(this.channel).append("] ");
        sb.append("rd = [").append(this.rd).append("] ");
        sb.append("rendering=").append(this.rendering).append(" ");
        sb.append("donerendering=").append(this.donerendering).append(" ");
        sb.append("startTime=").append(this.startTime).append(" ");
        sb.append("timeOut=").append(this.timeOut).append(" ");
       
        return sb.toString();
    }


  public String getChannelTitle() {
     
   
    if (log.isTraceEnabled()) {
      log.trace("Getting channel title for ChannelRenderer " + this);
    }
     
    // default to null, which indicates the ChannelRenderer doesn't have
    // a dynamic channel title available.
    String channelTitle = null;
      try {
          // block on channel rendering to allow channel opportunity to
          // provide dynamic title.
          int renderingStatus = completeRendering();
          if (renderingStatus == RENDERING_SUCCESSFUL) {
              channelTitle = this.worker.getChannelTitle();
          }
      } catch (Throwable t) {
          log.error("Channel rendering failed while getting title for channel renderer " + this, t);
      }
     
      // will be null indicating no dynamic title unless successfully obtained title.
      return channelTitle;

  }

    protected class Worker extends BaseTask {
        private boolean successful;
        private boolean done;
        private boolean setRuntimeDataComplete;
        private IChannel channel;
        private ChannelRuntimeData rd;
        private SAX2BufferImpl buffer;
        private String cbuffer;
       
        /**
         * The dynamic title of the channel, if any.  Null otherwise.
         */
        private String channelTitle = null;

        public Worker (IChannel ch, ChannelRuntimeData runtimeData) {
            this.channel=ch;  this.rd=runtimeData;
            successful = false; done = false; setRuntimeDataComplete=false;
            buffer=null; cbuffer=null;
        }

        public void setChannel(IChannel ch) {
            this.channel=ch;
        }

        public boolean isSetRuntimeDataComplete() {
            return this.setRuntimeDataComplete;
        }

        public void execute () throws Exception {
            try {
                if(rd!=null) {
                    channel.setRuntimeData(rd);
                }
                setRuntimeDataComplete=true;
               
                if(groupSemaphore!=null) {
                    groupSemaphore.checkInAndWaitOn(groupRenderingKey);
                }

                if(CACHE_CHANNELS) {
                    // try to obtain rendering from cache
                    if(channel instanceof ICacheable ) {
                        ChannelCacheKey key=((ICacheable)channel).generateKey();
                        if(key!=null) {
                            if(key.getKeyScope()==ChannelCacheKey.SYSTEM_KEY_SCOPE) {
                                ChannelCacheEntry entry=(ChannelCacheEntry)systemCache.get(key.getKey());
                                if(entry!=null) {
                                    // found cached page
                                    // check page validity
                                    if(((ICacheable)channel).isCacheValid(entry.validity) && (entry.buffer!=null)) {
                                        // use it
                                        if(ccacheable && (entry.buffer instanceof String)) {
                                            cbuffer=(String)entry.buffer;
                                            if (log.isDebugEnabled()) {
                                                log.debug("ChannelRenderer.Worker::run() : retrieved system-wide cached character content based on a key \""+key.getKey()+"\"");
                                            }
                                        } else if(entry.buffer instanceof SAX2BufferImpl) {
                                            buffer=(SAX2BufferImpl) entry.buffer;
                                            if (log.isDebugEnabled()) {
                                                log.debug("ChannelRenderer.Worker::run() : retrieved system-wide cached content based on a key \""+key.getKey()+"\"");
                                            }
                                        }
                                    } else {
                                        // remove it
                                        systemCache.remove(key.getKey());
                                        if (log.isDebugEnabled()) {
                                            log.debug("ChannelRenderer.Worker::run() : removed system-wide unvalidated cache based on a key \""+key.getKey()+"\"");
                                        }
                                    }
                                }
                            } else {
                                // by default we assume INSTANCE_KEY_SCOPE
                                ChannelCacheEntry entry=(ChannelCacheEntry)getChannelCache().get(key.getKey());
                                if(entry!=null) {
                                    // found cached page
                                    // check page validity
                                    if(((ICacheable)channel).isCacheValid(entry.validity) && (entry.buffer!=null)) {
                                        // use it
                                        if(ccacheable && (entry.buffer instanceof String)) {
                                            cbuffer=(String)entry.buffer;
                                            if (log.isDebugEnabled()) {
                                                log.debug("ChannelRenderer.Worker::run() : retrieved instance-cached character content based on a key \""+key.getKey()+"\"");
                                            }

                                        } else if(entry.buffer instanceof SAX2BufferImpl) {
                                            buffer=(SAX2BufferImpl) entry.buffer;
                                            if (log.isDebugEnabled()) {
                                                log.debug("ChannelRenderer.Worker::run() : retrieved instance-cached content based on a key \""+key.getKey()+"\"");
                                            }
                                        }
                                    } else {
                                        // remove it
                                        getChannelCache().remove(key.getKey());
                                        if (log.isDebugEnabled()) {
                                          log.debug("ChannelRenderer.Worker::run() : removed unvalidated instance-cache based on a key \""+key.getKey()+"\"");
                                        }
                                    }
                                }
                            }
                        }

                        // future work: here we should synchronize based on a particular cache key.
                        // Imagine a VERY popular cache entry timing out, then portal will attempt
                        // to re-render the page in many threads (serving many requests) simultaneously.
                        // If one was to synchronize on writing cache for a particular key, one thread
                        // would render and others would wait for it to complete.

                        // check if need to render
                        if((ccacheable && cbuffer==null && buffer==null) || ((!ccacheable) && buffer==null)) {
                            if (ccacheable && channel instanceof ICharacterChannel) {
                                StringWriter sw = new StringWriter(100);
                                PrintWriter pw = new PrintWriter(sw);
                                ((ICharacterChannel)channel).renderCharacters(pw);
                                pw.flush();
                                cbuffer = sw.toString();
                                // save cache
                                if (key != null) {
                                    if (key.getKeyScope() == ChannelCacheKey.SYSTEM_KEY_SCOPE) {
                                        systemCache.put(key.getKey(), new ChannelCacheEntry(cbuffer, key.getKeyValidity()));
                                        if (log.isDebugEnabled()) {
                                            log.debug("ChannelRenderer.Worker::run() : recorded system character cache based on a key \"" + key.getKey() + "\"");
                                        }
                                    } else {
                                        getChannelCache().put(key.getKey(), new ChannelCacheEntry(cbuffer, key.getKeyValidity()));
                                        if (log.isDebugEnabled()) {
                                            log.debug("ChannelRenderer.Worker::run() : recorded instance character cache based on a key \"" + key.getKey() + "\"");
                                        }
                                    }
                                }
                            } else {
                                // need to render again and cache the output
                                buffer = new SAX2BufferImpl ();
                                buffer.startBuffering();
                                channel.renderXML(buffer);

                                // save cache
                                if(key!=null) {

                                    if(key.getKeyScope()==ChannelCacheKey.SYSTEM_KEY_SCOPE) {
                                        systemCache.put(key.getKey(),new ChannelCacheEntry(buffer,key.getKeyValidity()));
                                        if (log.isDebugEnabled()) {
                                            log.debug("ChannelRenderer.Worker::run() : recorded system cache based on a key \""+key.getKey()+"\"");
                                        }
                                    } else {
                                        getChannelCache().put(key.getKey(),new ChannelCacheEntry(buffer,key.getKeyValidity()));
                                        if (log.isDebugEnabled()) {
                                            log.debug("ChannelRenderer.Worker::run() : recorded instance cache based on a key \""+key.getKey()+"\"");
                                        }
                                    }
                                }
                            }
                        }
                    } else {
                        if (ccacheable && channel instanceof ICharacterChannel) {
                            StringWriter sw = new StringWriter(100);
                            PrintWriter pw = new PrintWriter(sw);
                            ((ICharacterChannel)channel).renderCharacters(pw);
                            pw.flush();
                            cbuffer = sw.toString();
                        } else {
                            buffer = new SAX2BufferImpl ();
                            buffer.startBuffering();
                            channel.renderXML(buffer);
                        }
                    }
                } else  {
                    // in the case when channel cache is not enabled
                    buffer = new SAX2BufferImpl ();
                    buffer.startBuffering();
                    channel.renderXML (buffer);
                }
                successful = true;
            } catch (Exception e) {
                if(groupSemaphore!=null) {
                    groupSemaphore.checkIn(groupRenderingKey);
                }
                this.setException(e);
            }
           
            /*
             * Get the channel's ChannelRuntimeProperties, and handle them.
             */
            processChannelRuntimeProperties();
           
            done = true;
        }

        /**
     * Query the channel for ChannelRuntimePRoperties and process those
     * properties.
     *
     * Currently, only handles the optional @link{IChannelTitle} interface.
     */
    private void processChannelRuntimeProperties() {
      ChannelRuntimeProperties channelProps = this.channel.getRuntimeProperties();
           
            if (channelProps != null) {
              if (channelProps instanceof IChannelTitle) {
                 
                  this.channelTitle = ((IChannelTitle) channelProps).getChannelTitle();
                  if (log.isTraceEnabled()) {
                    log.trace("Read title " + this.channelTitle + ".");
                  }
                } else {
                  if (log.isTraceEnabled()) {
                    log.trace("ChannelRuntimeProperties were non-null but did not implement ITitleable.");
                  }
                }
            } else {
              if (log.isTraceEnabled()) {
                log.trace("ChannelRuntimeProperties were null from channel " + channel);
              }
            }
    }
       
       
        public boolean successful () {
            return this.successful;
        }

        public SAX2BufferImpl getBuffer() {
            return this.buffer;
        }

        /**
         * Returns a character output of a channel rendering.
         */
        public String getCharacters() {
            if(ccacheable) {
                return this.cbuffer;
            } else {
                log.error("ChannelRenderer.Worker::getCharacters() : attempting to obtain character data while character caching is not enabled !");
                return null;
            }
        }
      

        /**
         * Sets a character cache for the current rendering.
         */
        public void setCharacterCache(String chars) {
            cbuffer=chars;
            if(CACHE_CHANNELS) {
                // try to obtain rendering from cache
                if(channel instanceof ICacheable ) {
                    ChannelCacheKey key=((ICacheable)channel).generateKey();
                    if(key!=null) {
                        if (log.isDebugEnabled()) {
                            log.debug("ChannelRenderer::setCharacterCache() : called on a key \""+key.getKey()+"\"");
                        }
                        ChannelCacheEntry entry=null;
                        if(key.getKeyScope()==ChannelCacheKey.SYSTEM_KEY_SCOPE) {
                            entry=(ChannelCacheEntry)systemCache.get(key.getKey());
                            if(entry==null) {
                                if (log.isDebugEnabled()) {
                                    log.debug("ChannelRenderer::setCharacterCache() : setting character cache buffer based on a system key \""+key.getKey()+"\"");
                                }
                                entry=new ChannelCacheEntry(chars,key.getKeyValidity());
                            } else {
                                entry.buffer=chars;
                            }
                            systemCache.put(key.getKey(),entry);
                        } else {
                            // by default we assume INSTANCE_KEY_SCOPE
                            entry=(ChannelCacheEntry)getChannelCache().get(key.getKey());
                            if(entry==null) {
                                if (log.isDebugEnabled()) {
                                    log.debug("ChannelRenderer::setCharacterCache() : no existing cache on a key \""+key.getKey()+"\"");
                                }
                                entry=new ChannelCacheEntry(chars,key.getKeyValidity());
                            } else {
                                entry.buffer=chars;
                            }
                            getChannelCache().put(key.getKey(),entry);
                        }
                    } else {
                        if (log.isDebugEnabled()) {
                          log.debug("ChannelRenderer::setCharacterCache() : channel cache key is null.");
                        }
                    }
                }
            }
        }

        public boolean done () {
            return this.done;
        }
       
        /**
         * Get a Sring representing the dynamic channel title reported by the
         * IChannel this ChannelRenderer rendered.  Returns null if the channel
         * reported no such title or the channel isn't done rendering.
         *
         * @return dynamic channel title, or null if no title available.
         */
        String getChannelTitle() {
         
          if (log.isTraceEnabled()) {
            log.trace("Getting channel title (" + this.channelTitle + "] for " + this);
          }
         
          // currently, just provides no dynamic title if not done rendering
          if (this.done) {
              return this.channelTitle;
          } else {
              return null;
          }
        }
    }
}
TOP

Related Classes of org.jasig.portal.ChannelRenderer$Worker

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.