/*
 * Copyright 2000-2004 The Apache Software Foundation.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.jetspeed.portal.portlets;

//standard java stuff
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;

//Element Construction Set
import org.apache.jetspeed.util.JetspeedClearElement;
import org.apache.ecs.ConcreteElement;

//standard Jetspeed stuff
import org.apache.jetspeed.util.MimeType;
import org.apache.jetspeed.util.SimpleTransform;
import org.apache.jetspeed.cache.disk.JetspeedDiskCache;
import org.apache.jetspeed.portal.PortletException;
import org.apache.jetspeed.xml.JetspeedXMLEntityResolver;
import org.apache.jetspeed.capability.CapabilityMap;
import org.apache.jetspeed.capability.CapabilityMapFactory;
import org.apache.jetspeed.services.logging.JetspeedLogFactoryService;
import org.apache.jetspeed.services.logging.JetspeedLogger;
import org.apache.jetspeed.services.rundata.JetspeedRunData;

//turbine
import org.apache.turbine.util.RunData;

//JAXP support
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

//XML stuff
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
<p>Portlet which renders RDF Site Summary.</p>
<p>This portlet uses XML stylesheet for transforming the RSS
content into display markup depending on the MimeType requested
by the user-agent</p>
<p>It accepts the following parameters :
<dl>
<dt>itemDisplayed</dt>
<dd>The number of items from the RSS file to display on screen. Default 15 for HTML, 5 for WML</dd>
<dt>showDescription</dt>
<dd>Should the portlet show the item descriptions. Must be true or false. Default: true for HTML, false for WML</dd>
<dt>showTitle</dt>
<dd>Should the portlet show the channel description. Must be true or false. Default: true for HTML, false for WML</dd>
<dt>stylesheet[.<mime>]</dt>
<dd>The stylesheet URL. If a mime-type is specified, the stylesheet
is only used for this mime-type</dd>
</dl>
@author <A HREF="mailto:raphael@apache.org">Raphaël Luta</A>
@version $Id: NewRSSPortlet.java,v 1.22 2004/02/23 04:03:34 jford Exp $
*/
public class NewRSSPortlet extends FileWatchPortlet 
{

    /**
     * Static initialization of the logger for this class
     */    
    private static final JetspeedLogger logger = JetspeedLogFactoryService.getLogger(NewRSSPortlet.class.getName());
    
    public final static String ERROR_NOT_VALID = "This does not appear to be an RSS document";
    public final static String INVALID_TYPE = "Unable to display for this browser";

    private Document document = null;
    private Hashtable stylesheets = null;
    private Hashtable params = null;

    /**
        This method loads the init parameters and
        parse the document tied to this portlet
    */
    public void init( ) throws PortletException {

        // first make sure we propagate init
        super.init();

        DocumentBuilder parser = null;
        String url = null;

        // load stylesheets available
        stylesheets = new Hashtable();
        params = new Hashtable();
        Iterator i = this.getPortletConfig().getInitParameterNames();
        while (i.hasNext()) {
            String name = (String)i.next();
            String base = MimeType.HTML.toString();

            if (name.startsWith("stylesheet")) {
                int idx=-1;
                if ((idx=name.indexOf("."))>-1) {
                    base= name.substring(idx+1,name.length());
                }
                stylesheets.put(base, getPortletConfig().getInitParameter(name));
            } else {
                params.put(name.toLowerCase(), getPortletConfig().getInitParameter(name));
            }
        }

        // read content, clean it, parse it and cache the DOM
        try
        {
            final DocumentBuilderFactory docfactory = DocumentBuilderFactory.newInstance();
            //Have it non-validating
            docfactory.setValidating(false);
            parser= docfactory.newDocumentBuilder();
            parser.setEntityResolver(new JetspeedXMLEntityResolver() );

            url = getPortletConfig().getURL();
            String content = JetspeedDiskCache.getInstance().getEntry( url ).getData();
           CapabilityMap xmap =
               CapabilityMapFactory.getCapabilityMap(CapabilityMapFactory.AGENT_XML);
            setContent( new JetspeedClearElement(content), xmap );
            InputSource isrc = new InputSource( this.cleanse( content ) );
            isrc.setSystemId( url );
            isrc.setEncoding("UTF-8");
            this.document = parser.parse( isrc );
            this.setMetainfo(document);

        } catch ( Throwable t )
        {

            String message = "RSSPortlet:  Couldn't parse out XML document -> " +
                              url;

            logger.error( message, t );
            throw new PortletException( t.getMessage() );
        }


    }

    /**
     * Parse out title and description
     * 
     * @param document
     */
    private void setMetainfo(Document document) throws PortletException
    {
        //Determine title and description for this portlet
        String title = null;
        String description = null;

        //now find the channel node.
        Node channel = null;

        NodeList list = document.getElementsByTagName( "channel" );

        if ( list.getLength() != 1 ) {
            throw new PortletException( ERROR_NOT_VALID );
        }

        channel = list.item( 0 );

        Node tn = getNode( channel, "title" );

        if ( tn == null ) {
            throw new PortletException( ERROR_NOT_VALID );
        } 
        else 
        {
            Node fc = tn.getFirstChild();
            if (fc != null)
            {
                title = fc.getNodeValue();
            }
        }

        Node dn = getNode( channel, "description" );

        if ( dn != null ) 
        {
            Node fc = dn.getFirstChild();
            if (fc != null)
            {
                description = fc.getNodeValue();
            }
        }

        this.setTitle( title );
        this.setDescription( description );
    }

    /**
    This methods outputs the content of the portlet for a given
    request.

    @param data the RunData object for the request
    @return the content to be displayed to the user-agent
    */
    public ConcreteElement getContent( RunData data ) 
    {
        if (org.apache.jetspeed.util.PortletSessionState.getPortletConfigChanged(this, data))
        {
            try 
            {
                init(); 
            }
            catch (PortletException pe)
            {
                logger.error("Exception",  pe);
            }
        }
        CapabilityMap map = ((JetspeedRunData)data).getCapability();
        String type = map.getPreferredType().toString();
        ConcreteElement content = new JetspeedClearElement(INVALID_TYPE);
        String stylesheet = (String)stylesheets.get(type);

        if (stylesheet != null) {
            content = getContent( data, map );
            if ( content == null ) {
                try {
                    content = new JetspeedClearElement(
                        SimpleTransform.transform( this.document,
                                                   stylesheet,
                                                   this.params ) );
                    setContent( content, map );
                } catch ( SAXException e ) {
                    logger.error("Exception",  e);
                    content = new JetspeedClearElement(e.getMessage());
                }
            }
        }
        else
        {
            if (map.getPreferredType().equals(MimeType.XML))
            {
                return getContent( data, map );
            }
        }

        return content;
    }

    /**
    This portlet supports has many types as those
    it has stylesheets defined for in its parameters

    @see Portlet#supportsType
    @param mimeType the MIME type queried
    @return true if the portlet knows how to display
    content for mimeType
    */
    public boolean supportsType( MimeType mimeType ) {

        Enumeration en = stylesheets.keys();
        while(en.hasMoreElements()) {
            String type = (String)en.nextElement();
            if (type.equals(mimeType.toString())) return true;
        }

        return false;
    }

    /**
    Utility method for traversing the document parsed
    DOM tree and retrieving a Node by tagname

    @param start the parent node for the search
    @param name the tag name to be searched for
    @return the first child node of start whose tagname
    is name
    */
    private final Node getNode( Node start, String name ) {

        NodeList list = start.getChildNodes();

        for ( int i = 0; i < list.getLength(); ++i ) {

            Node node = list.item( i );

            if ( node.getNodeName().equals( name ) ) {
                return node;
            }
        }
        return null;
    }

    /**
    Given a URL to some content, clean the content to Xerces can handle it
    better.  Right now this involves:
    <ul>
        <li>
            If the document doesn't begin with "<?xml version=" truncate the
            content until this is the first line
        </li>

    </ul>

    */
    private Reader cleanse( String content ) throws IOException {

        String filtered = null;

        //specify the XML declaration to search for... this is just a subset
        //of the content but it will always exist.
        String XMLDECL = "<?xml version=";

        int start = content.indexOf( XMLDECL );

        if ( start <= 0 ) {
            filtered = content;
        } else {
            filtered = content.substring( start, content.length() );
        }

        return new StringReader( filtered );
    }

}

