/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000-2001 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Apache" and "Apache Software Foundation" and
 *     "Apache Jetspeed" must not be used to endorse or promote products
 *    derived from this software without prior written permission. For
 *    written permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache" or
 *    "Apache Jetspeed", nor may "Apache" appear in their name, without
 *    prior written permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */
package org.apache.jetspeed.portal.portlets.viewprocessor;

import java.io.IOException;

//standard java stuff
import java.io.Reader;
import java.io.StringReader;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;


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

import org.apache.ecs.ConcreteElement;

import org.apache.jetspeed.cache.disk.JetspeedDiskCache;
import org.apache.jetspeed.capability.CapabilityMap;
import org.apache.jetspeed.capability.CapabilityMapFactory;
import org.apache.jetspeed.portal.Portlet;
import org.apache.jetspeed.portal.PortletException;
import org.apache.jetspeed.portal.portlets.*;
import org.apache.jetspeed.portal.portlets.GenericMVCContext;
import org.apache.jetspeed.portal.portlets.viewprocessor.*;
import org.apache.jetspeed.services.rundata.JetspeedRunData;

// Element Construction Set
import org.apache.jetspeed.util.JetspeedClearElement;
import org.apache.jetspeed.util.MimeType;
import org.apache.jetspeed.util.SimpleTransform;
import org.apache.jetspeed.xml.JetspeedXMLEntityResolver;

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


// 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: RSSViewProcessor.java,v 1.1.2.2 2003/02/24 21:52:27 tkuebler Exp $
*/
public class RSSViewProcessor
    implements ViewProcessor
  {

    private static final String XMLDECL = "<?xml version=";
    public static final String ERROR_NOT_VALID = "This does not appear to be an RSS document";
    public static final 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(Portlet portlet)
              throws PortletException
      {

        DocumentBuilder parser = null;
        String url = null;

        // load stylesheets available
        stylesheets = new Hashtable();
        params = new Hashtable();

        Iterator i = portlet.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, portlet.getPortletConfig().getInitParameter(name));
              }
            else
              {
                params.put(name.toLowerCase(), portlet.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 = portlet.getPortletConfig().getURL();

            String content = JetspeedDiskCache.getInstance().getEntry(url).getData();
            CapabilityMap xmap = CapabilityMapFactory.getCapabilityMap(CapabilityMapFactory.AGENT_XML);

            // no cache yet // portlet.setContent( new JetspeedClearElement(content), xmap );
            InputSource isrc = new InputSource(this.cleanse(content));
            isrc.setSystemId(url);
            isrc.setEncoding("UTF-8");
            this.document = parser.parse(isrc);
          }
        catch (Throwable t)
          {

            String message = "RSSPortlet:  Couldn't parse out XML document -> " + url;
            Log.error(message, t);
            throw new PortletException(t.getMessage());
          }

        //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
          {
            title = tn.getFirstChild().getNodeValue();
          }

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

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

        portlet.setTitle(title);
        portlet.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 Object processView(GenericMVCContext context)
      {

        try
          {
            init((Portlet) context.get("portlet"));
          }
        catch (PortletException pe)
          {
            pe.printStackTrace();
            Log.error("RSSViewProcessor - error: " + pe);
          }

        RunData data = (RunData) context.get("data");
        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)
          {

            try
              {
                content = new JetspeedClearElement(SimpleTransform.transform(this.document, stylesheet, this.params));

                // no caching yet // setContent( content, map );
              }
            catch (SAXException e)
              {
                Log.error(e);
                content = new JetspeedClearElement(e.getMessage());
              }
          }
        else
          {
            content = new JetspeedClearElement("stylesheet not defined");
          }

        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;
        int start = content.indexOf(XMLDECL);

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

        return new StringReader(filtered);
      }
  }
