/*
* 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;
//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.PortletException;
import org.apache.jetspeed.services.logging.JetspeedLogFactoryService;
import org.apache.jetspeed.services.logging.JetspeedLogger;
import org.apache.jetspeed.services.rundata.JetspeedRunData;
//Element Construction Set
import org.apache.jetspeed.util.JetspeedClearElement;
//standard Jetspeed stuff
import org.apache.jetspeed.util.MimeType;
import org.apache.jetspeed.util.SimpleTransform;
import org.apache.jetspeed.xml.JetspeedXMLEntityResolver;
//turbine
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: 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
*/
@Override
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
*/
@Override
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
*/
@Override
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);
}
}