// Copyright (c) 2001 Oracle Corporation
package oracle.portal.provider.v2.render.http;

import java.io.*;
import oracle.portal.provider.v2.PortletException;
import oracle.portal.provider.v2.render.*;
import oracle.portal.provider.v2.utils.ValidationError;
import oracle.portal.utils.xml.v2.*;

/**
 * When maintained by a {@link oracle.portal.provider.v2.render.RenderManager},
 * a <code>FileRenderer</code> renders a single page's contents from a static
 * file.<p>
 *
 * The properties <code>contentType</code>, <code>pageExpires</code>,
 * <code>charSet</code> may be set at page level or defaulted at {@link
 * oracle.portal.provider.v2.render.RenderManager} level.  However the page
 * name must be set at page level via a call to <code>{@link
 * #setName(String)}</code> or by use of the <code>&ltname&gt</code>
 * <code>provider.xml</code> tag.
 *
 */
public class FileRenderer extends BaseManagedRenderer
{
    public static final int CHAR_BUFFER_SIZE = 8192;

    private String mAppRoot = null;
    private String mCharSet = null;
    private String mPageName = null;

    private char[] mCache = null;
    private long mLastModified = -1;

    /**
     * Null Constructor. The {@link BaseManagedRenderer#preInitialize(Object)}
     * method should be called after creating an object with this constructor
     * and the {@link BaseManagedRenderer#postInitialize()} method should be
     * called after setting up page properties with the various accessor
     * methods. Normally a {@link DefaultNodeHandler} takes care of these
     * details.
     */
    public FileRenderer()
    {
    }

    /**
     * Creates and initializes a <code>FileRenderer</code> owned by the given
     * {@link RenderManager}, describing a page with the given name.
     * @param parent the <code>RenderManager</code> that owns this page
     * @param pageName the <code>appPath</code> relative name of the renderer's
     * page
     * @exception PortletException if an error occurs while initializing the
     * object
     */
    public FileRenderer(RenderManager parent, String pageName) throws PortletException
    {
        try
        {
            preInitialize(parent);
            setName(pageName);
            postInitialize();
        }
        catch(NodeHandlerException e)
        {
            throw new PortletException(e);
        }
    }

    /**
     * Sets the name of the page described by this <code>Page</code>.
     *
     * @param pageName <code>appRoot</code> relative name of the page
     */
    public void setName(String pageName)
    {
        mPageName = pageName;
    }

    /**
     * Gets the name of the page described by this <code>Page</code>.
     *
     * @return <code>appRoot</code> relative name of the page
     */
    public String getName()
    {
        return mPageName;
    }

    /**
     *  Sets the root physical path for the renderer's pages.
     *
     *  @param path the root physical path for the renderer's pages.
     */
    public void setAppRoot(String path)
    {
        mAppRoot = path;
    }

    /**
     *  Gets the root physical path for the renderer's pages.
     *
     *  @return the root physical path for the renderer's pages.
     */
    public String getAppRoot()
    {
        return mAppRoot;
    }

    /**
     *  Sets the Default IANA character encoding to be used for this renderer's
     *  pages.
     *
     *  @param charSet IANA character encoding to be used for this renderer's
     *  pages
     */
    public void setCharSet(String charSet)
    {
        mCharSet = charSet;
    }

    /**
     *  Gets the Default IANA character encoding to be used for this renderer's
     *  pages. If a character encoding hasn't been set explicitly on this
     *  <code>BaseManagedRenderer</code>, then the value returned by calling
     *  the {@link RenderManager#getCharSet()} method on the parent
     *  <code>RenderManager</code> is returned.
     *
     *  @return IANA character encoding to be used for this renderer's pages
     */
    public String getCharSet()
    {
        if(mCharSet == null)
        {
            return mParent.getCharSet();
        }
        return mCharSet;
    }

    /**
     * Render the specified page's contents using the current appRoot and page
     * name. File Renderer does this by copying the contents of the static file
     * to the output stream.
     *
     * @param pr a PortletRenderRequest
     * @exception PortletException if an error occurs while rendering.
     */
    public void renderBody(PortletRenderRequest pr)
        throws PortletException
    {
        try
        {
            // Obtain the writer for this request.
            Writer out = pr.getWriter();

            // Obtain the file.
            File file = new File(concatPaths(mAppRoot, mPageName,
                                             File.separator));

            // Cache the File's contents to avoid reading the file with each
            // subsequent request, unless the file has been modified in the
            // meantime.
            if (mCache == null || (mLastModified != file.lastModified()))
            {
                InputStreamReader isr = null;
                CharArrayWriter cacheOut = null;
                try
                {
                    BufferedInputStream bis =
                        new BufferedInputStream(new FileInputStream(file),
                                                CHAR_BUFFER_SIZE * 2);
                    isr = new InputStreamReader(bis, getCharSet());
                    cacheOut = new CharArrayWriter();
                    char[] buf = new char[CHAR_BUFFER_SIZE];
                    int read = 0;
                    while ((read = isr.read(buf)) != -1)
                    {
                        cacheOut.write(buf, 0, read);
                    }
                    mCache = cacheOut.toCharArray();
                    mLastModified = file.lastModified();
                }
                finally
                {
                    // Close any streams we have opened.
                    try
                    {
                        if (isr != null)
                        {
                            isr.close();
                        }
                        if (cacheOut != null)
                        {
                            cacheOut.close();
                        }
                    }
                    catch (IOException e)
                    {
                        // throw this one away.
                    }
                }
            }

            // Write the contents of the cache to the required output stream.
            out.write(mCache);
        }
        catch (IOException ioe)
        {
            throw new PortletException(ioe);
        }
    }

    /**
     * Validates the internal state of this <code>FileRenderer</code>
     * instance. Errors found are reported using the supplied {@link
     * oracle.portal.provider.v2.utils.ValidationError} instance.
     *
     * @param context The context of the calling instance.
     * @param errors The current list of errors.
     */
    public void validate(String context, ValidationError errors)            
    {                                                                       
        String ctx = ValidationError.newContext(context, "FileRenderer", null);
                            
        // Call super type
        super.validate(ctx, errors);
                                                                            
        // Check the validity of the member variables                       
        if (mAppRoot == null)
        {                                                                   
            errors.addError(ctx, "appRoot must not be null", null);
        }

        if (mPageName == null)
        {                                                                   
            errors.addError(ctx, "page name must not be null", null);
        }  

        if(mAppRoot != null && mPageName != null )
        {
            // Validate that the file exists and is readable.
            File file = new File(concatPaths(mAppRoot, mPageName,
                                             File.separator));
            if(!file.exists())
            {
                errors.addError(ctx, "file does not exist", file.getName());
            }  
            else if(!file.canRead())
            {
                errors.addError(ctx, "file is not readable", file.getName());
            }
        }
    }

    private String concatPaths(String root, String relative, String pathSep)
    {
        int sLen = root.length();
        boolean pathSuffixed = root.charAt(sLen - 1) == pathSep.charAt(0);
        boolean pathPrefixed = relative.charAt(0) == pathSep.charAt(0);

        if (sLen == 1 && pathSuffixed && pathPrefixed)
        {
            return relative;
        }
        else if ((pathSuffixed && !pathPrefixed) || 
                 (!pathSuffixed && pathPrefixed))
        {
            return root + relative;
        }
        else if (!pathSuffixed && !pathPrefixed)
        {
            return root + pathSep + relative;
        }
        else
        {
            // root path is suffixed and the relative path is prefixed
            return root.substring(0, sLen - 1) + relative;
        }
    }

}
