/* Copyright (c) 2002  by Oracle Corporation. All Rights Reserved. */

package oracle.portal.provider.v1.http;

import oracle.webdb.utils.SimpleStringBuffer;
import oracle.portal.provider.v1.ProviderLog;
import javax.servlet.http.*;
import java.io.IOException;
import java.io.PrintWriter;

/**
 *  This class is used to collect and output performance statistics for a
 *  show request.<p>
 */
public class PerformanceLogger
{
    private static final String STR_NOT_LOGGED = "(null)";
    private static final int INT_NOT_LOGGED = -1;

    private static final String VALIDATION_HIT = "HIT,PING";
    private static final String VALIDATION_STALE = "MISS,STALE";
    private static final String VALIDATION_NEW = "MISS,NEW";
    private static final String EXPIRES_MISS = "MISS,EXPIRES";

    public static final int VALIDATION_CACHE_MISS_STALE = 0;
    public static final int VALIDATION_CACHE_MISS_NEW = 1;
    public static final int VALIDATION_CACHE_HIT = 2;

    private String mRequestId = STR_NOT_LOGGED;;
    private int mAction = INT_NOT_LOGGED;
    private String mOpName = STR_NOT_LOGGED;

    private String mPortletName = STR_NOT_LOGGED;
    private String mPortletInstanceName = STR_NOT_LOGGED;

    private long mPortletId = INT_NOT_LOGGED;
    private long mProviderId = INT_NOT_LOGGED;
    private String mUser = STR_NOT_LOGGED;

    private long mStartRequestTime;
    private long mRequestTime = INT_NOT_LOGGED;

    private long mFrameworkTime = INT_NOT_LOGGED;

    private long mStartProcessingTime;
    private long mProcessingTime = INT_NOT_LOGGED;

    private long mStartRenderHeaderTime;
    private long mRenderHeaderTime = INT_NOT_LOGGED;

    private long mStartRenderFooterTime;
    private long mRenderFooterTime = INT_NOT_LOGGED;

    private long mStartReadCustomizationTime;
    private long mReadCustomizationTime = INT_NOT_LOGGED;

    private long mStartWriteCustomizationTime;
    private long mWriteCustomizationTime = INT_NOT_LOGGED;

    private String mCacheType = STR_NOT_LOGGED;
    private String mCacheLevel = STR_NOT_LOGGED;

    // Expiry based caching
    private String mExpiryCacheTime = STR_NOT_LOGGED;

    // Validation based caching
    private String mValidationCacheRequestKey = STR_NOT_LOGGED;
    private String mValidationCacheResponseKey = STR_NOT_LOGGED;

    /**
     * Set the request action
     * 
     * @param action request action type, e.g, show portlet, register provider
     */
    public void setAction(int action)
    {
        mAction = action;

        switch (mAction)
        {
        case HttpProviderConstants.ACTION_REGISTER_PROVIDER:
            mOpName = "registerProvider";
            break;
        case HttpProviderConstants.ACTION_DEREGISTER_PROVIDER:
            mOpName = "deregisterProvider";
            break;
        case HttpProviderConstants.ACTION_GET_PORTLETS:
            mOpName = "getPortlets";
            break;
        case HttpProviderConstants.ACTION_GET_PORTLET:
            mOpName = "getPortlet";
            break;
        case HttpProviderConstants.ACTION_IS_PORTLET_RUNNABLE:
            mOpName = "isPortletRunnable";
            break;            
        case HttpProviderConstants.ACTION_GET_API_VERSION:
            mOpName = "getApiVersion";
            break;
        case HttpProviderConstants.ACTION_REGISTER_PORTLET:
            mOpName = "registerPortlet";
            break;
        case HttpProviderConstants.ACTION_DEREGISTER_PORTLET:
            mOpName = "deregisterPortlet";
            break;
        case HttpProviderConstants.ACTION_COPY_PORTLET:
            mOpName = "copyPortlet";
            break;
        case HttpProviderConstants.ACTION_INIT_SESSION:
            mOpName = "initSession";
            break;
        case HttpProviderConstants.ACTION_SHOW_PORTLET:
            mOpName = "showPortlet";
            break;
        default:
            mOpName = "invalidOperation";
        }
    }

    /**
     *  Start the overall request timer.
     */
    public void startRequest()
    {
        mStartRequestTime = System.currentTimeMillis();
    }

    /**
     *  Stop request timer.
     */
    public void finishRequest()
    {
        mRequestTime = System.currentTimeMillis() - mStartRequestTime;
    }

    /**
     *  Start a timer to measure the time taken to process an incoming design
     *  time or show request.
     */
    public void startProcessing()
    {
        mStartProcessingTime = System.currentTimeMillis();
    }

    /**
     *  Stop the timer once an incoming request has been processed.
     */
    public void finishProcessing()
    {
        mProcessingTime = System.currentTimeMillis() - mStartProcessingTime;
    }

    /**
     *  Start a timer to measure the time taken to render a portlet header.
     *  This method is only called during show requests.
     */
    public void startRenderHeader()
    {
        mStartRenderHeaderTime = System.currentTimeMillis();
    }

    /**
     *  Stop the timer once a portlet header has been rendered.
     */
    public void finishRenderHeader()
    {
        mRenderHeaderTime = System.currentTimeMillis() - mStartRenderHeaderTime;
    }

    /**
     *  Start a timer to measure the time taken to render a portlet footer.
     *  This method is only called during a show request.
     */
    public void startRenderFooter()
    {
        mStartRenderFooterTime = System.currentTimeMillis();
    }

    /**
     *  Stop the timer once a portlet footer has been rendered.
     */
    public void finishRenderFooter()
    {
        mRenderFooterTime = System.currentTimeMillis() - mStartRenderFooterTime;
    }

    /**
     *  Start a timer to measure the time taken to access a portlet 
     *  customisation object. This may be called more than once during a 
     *  show request.
     */
    public void startReadCustomization()
    {
        mStartReadCustomizationTime = System.currentTimeMillis();
    }

    /**
     *  Stop the timer once a portlet customisation object has been read. This
     *  may be called more than once during a show request.
     */
    public void finishReadCustomization()
    {
        mReadCustomizationTime += System.currentTimeMillis() - 
                                                  mStartReadCustomizationTime;
    }

    /**
     *  Start a timer to measure the time taken to write information to a 
     *  portlet customosation object. This may be called more than once during
     *  a show request.
     */
    public void startWriteCustomization()
    {
        mStartWriteCustomizationTime = System.currentTimeMillis();        
    }

    /**
     *  Stop the timer once a portlet customisation object has been written to.
     *  This may be called more than once during a show request.
     */
    public void finishWriteCustomization()
    {
        mWriteCustomizationTime += System.currentTimeMillis() - 
                                                  mStartWriteCustomizationTime;
    }

    /**
     *  Set the level at which the portlet uses expiry or validation caching.
     *  This may be set as either USER or SYSTEM. Any other value will be 
     *  ignored.
     *
     *  @param level the caching level.
     */
    public void setCacheLevel(int level)
    {
        switch (level)
        {
        case HttpProvider.USER_LEVEL:
            mCacheLevel = HttpProvider.CACHE_LEVEL_USER.toLowerCase();
            break;
        case HttpProvider.SYSTEM_LEVEL:
            mCacheLevel = HttpProvider.CACHE_LEVEL_SYSTEM.toLowerCase();
            break;
        }
    }
    
    /** 
     *  Set the time in minutes that a portlet is valid for.  Used in expiry
     *  caching only.
     *
     *  @param minutes expiry time in minutes
     */
    public void setExpiryCacheTime(int minutes)
    {
        mCacheType = EXPIRES_MISS;        
        mExpiryCacheTime = Integer.toString(minutes);
    }

    /**
     *  For validation caching, set the hit status for the portlet. It may be
     *  a hit, a stale miss or a new miss.  Any other value will be ignored.
     *  
     *  @param status the validation cache hit status
     */
    public void setValidationCacheHitStatus(int status)
    {
        switch (status)
        {
        case VALIDATION_CACHE_HIT:
            mCacheType = VALIDATION_HIT;
            break;
        case VALIDATION_CACHE_MISS_STALE:
            mCacheType = VALIDATION_STALE;
            break;
        case VALIDATION_CACHE_MISS_NEW:
            mCacheType = VALIDATION_NEW;
            break;
        }
    }

    /**
     *  Sets the cache version used in the incoming request for
     *  validation caching.
     *  @param key the cache version
     */
    public void setValidationCacheRequestKey(String key)
    {
        mValidationCacheRequestKey = key;
    }

    /**
     *  Sets the cache version in the response for validation caching.
     *
     *   @param key the cache version 
     */
    public void setValidationCacheResponseKey(String key)
    {
        mValidationCacheResponseKey = key;
    }

    /**
     *  Sets the request id for show requests. These ids are oracle specific
     *  and used to track requests across the Portal infrastructure.
     *
     *  @param id the request id
     */
    public void setRequestId(String id)
    {
        mRequestId = id;
    }

    /**
     *  Sets the portlet name for a show request.
     *  
     *  @param name the portlet name
     */
    public void setPortletName(String name)
    {
        mPortletName = name;
    }

    /**
     *  Sets the portlet instance name for a show request.
     *
     *  @param name the portlet instance name
     */
    public void setPortletInstanceName(String name)
    {
        mPortletInstanceName = name;
    }

    /**
     *  Sets the portlet id for a show request.
     *
     *  @param id the portlet id
     */
    public void setPortletId(long id)
    {
        mPortletId = id;
    }

    /**
     *  Sets the provider id for a show request.
     *
     *  @param id the provider id
     */
    public void setProviderId(long id)
    {
        mProviderId = id;
    }

    /**
     *  Sets the user name for a show request.
     *
     *  @param id the user name
     */
    public void setUser(String user)
    {
        mUser = user;
    }

    /**
     *  Writes the performance information to a <code>ProviderLog</code> 
     *  instance.
     *  
     *  @param logger the object to be used for recording the info
     */
    public void writeLog(ProviderLog logger)
    {
        SimpleStringBuffer logInfo = new SimpleStringBuffer(500);
        logInfo.append("[perf-provider] ");
        logInfo.append(createCommonInfo());
        logInfo.append(createShowInfo());
        logger.log(logInfo.toString());
    }

    /**
     *  Appends the information to the portlet content so that it may be
     *  seen by the user.
     *
     *  @param response the response object for the portlet content
     */
    public void writeResponse(HttpServletResponse response)
        throws IOException
    {
        SimpleStringBuffer resInfo = new SimpleStringBuffer(500);

        resInfo.append("<b><u>Web Provider Information for Portlet: ");
        resInfo.append(longToString(mPortletId, null));
        resInfo.append("</u></b><br>" +
                       "<i>Warning: Will be inaccurate if portlet content " + 
                       "retrieved from middle tier cache. Check the information " +
                       "below</i><br>" + 
                       " <b>Timing: </b>request time:");
        resInfo.append(mRequestTime);
        resInfo.append("ms, rendering time: ");
        resInfo.append(mProcessingTime);
        resInfo.append("ms<br><b>Cache Status: </b>");
        if (STR_NOT_LOGGED.equals(mCacheType))
        {
            resInfo.append("NONE<br>");
        }
        else
        {
            resInfo.append(mCacheType);
            resInfo.append("<br><b>Cache Details: </b>cache level: ");
            resInfo.append(mCacheLevel);
            if (EXPIRES_MISS.equals(mCacheType))
            {
                resInfo.append(", Expiry Time: ");
                resInfo.append(mExpiryCacheTime);
                resInfo.append("min<br>");
            }
            else
            {
                resInfo.append(", cache key received: ");
                resInfo.append(mValidationCacheRequestKey);
                resInfo.append(", cache key sent: ");
                resInfo.append(mValidationCacheResponseKey);
                resInfo.append("<br>");
            }
        }
        resInfo.append("<br>");

        PrintWriter out = response.getWriter();
        out.println(resInfo.toString());
    }

    private final String createCommonInfo()
    {
        // Format performance stats as strings delimited by the new line
        // character.

        SimpleStringBuffer statsMsg = new SimpleStringBuffer(450);
        statsMsg.append("id=");
        statsMsg.append(mRequestId);
        statsMsg.append(" providerId=");
        statsMsg.append(longToString(mProviderId, null));
        statsMsg.append(" portletId=");
        statsMsg.append(longToString(mPortletId, null));
        statsMsg.append(" portletName=");
        statsMsg.append(mPortletName);
        statsMsg.append(" portletInstance=");
        statsMsg.append(mPortletInstanceName);
        statsMsg.append(" user=");
        statsMsg.append(mUser);
        statsMsg.append(" operation=");            
        statsMsg.append(mOpName);
        statsMsg.append(" requestTime=");
        statsMsg.append(longToString(mRequestTime, "ms"));
        statsMsg.append(" processingTime=");
        statsMsg.append(longToString(mProcessingTime, "ms"));

        // Calculate the framework time if both processing and request times
        // have been logged. Otherwise use the default. For instance, if an 
        // exception is thrown during a request, one or both may not be logged.
        if ((mRequestTime != INT_NOT_LOGGED) 
            && (mProcessingTime != INT_NOT_LOGGED))
        {
            mFrameworkTime = mRequestTime - mProcessingTime;
        }
        statsMsg.append(" frameworkTime=");
        statsMsg.append(longToString(mFrameworkTime, "ms"));
        
        return(statsMsg.toString());
    }

    private final String createShowInfo()
    {
        // Specific stats for a show request, including cache info.
        
        // Now write the message to the log    
        SimpleStringBuffer showInfo = new SimpleStringBuffer(500);

        // Show request info
        showInfo.append(" headerRenderTime=");
        showInfo.append(longToString(mRenderHeaderTime, "ms"));
        showInfo.append(" footerRenderTime=");
        showInfo.append(longToString(mRenderFooterTime, "ms"));
        showInfo.append(" readCustTime=");
        showInfo.append(longToString(mReadCustomizationTime, "ms"));
        showInfo.append(" writeCustTime=");
        showInfo.append(longToString(mWriteCustomizationTime, "ms"));
        
        // Cache info
        showInfo.append(" cache=");
        showInfo.append(mCacheType);
        showInfo.append(" cacheLevel=");
        showInfo.append(mCacheLevel);
        showInfo.append(" expiryTime=");
        showInfo.append(mExpiryCacheTime);
        if (!STR_NOT_LOGGED.equals(mExpiryCacheTime))
        {
            showInfo.append("min");
        }
        showInfo.append(" cacheRequestKey=");
        showInfo.append(mValidationCacheRequestKey);
        showInfo.append(" cacheResponseKey=");
        showInfo.append(mValidationCacheResponseKey);

        return(showInfo.toString());
    }
    
    private final String longToString(long value, String units)
    {
        // Returns a long id as a string, a value with units or '(null)'
        if(value == -1)
        {
            return STR_NOT_LOGGED;
        }
        else
        {
            SimpleStringBuffer longValue = new SimpleStringBuffer(10);
            longValue.append(Long.toString(value));
            if (units != null)
            {
                longValue.append(units);
            }
            return longValue.toString();
        }
    }
}
