1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.jetspeed.portal.portlets;
18
19
20 import java.io.IOException;
21 import java.io.Reader;
22 import java.io.StringReader;
23 import java.util.Enumeration;
24 import java.util.Hashtable;
25 import java.util.Iterator;
26
27
28 import org.apache.jetspeed.util.JetspeedClearElement;
29 import org.apache.ecs.ConcreteElement;
30
31
32 import org.apache.jetspeed.util.MimeType;
33 import org.apache.jetspeed.util.SimpleTransform;
34 import org.apache.jetspeed.cache.disk.JetspeedDiskCache;
35 import org.apache.jetspeed.portal.PortletException;
36 import org.apache.jetspeed.xml.JetspeedXMLEntityResolver;
37 import org.apache.jetspeed.capability.CapabilityMap;
38 import org.apache.jetspeed.capability.CapabilityMapFactory;
39 import org.apache.jetspeed.services.logging.JetspeedLogFactoryService;
40 import org.apache.jetspeed.services.logging.JetspeedLogger;
41 import org.apache.jetspeed.services.rundata.JetspeedRunData;
42
43
44 import org.apache.turbine.util.RunData;
45
46
47 import javax.xml.parsers.DocumentBuilder;
48 import javax.xml.parsers.DocumentBuilderFactory;
49
50
51 import org.w3c.dom.Document;
52 import org.w3c.dom.Node;
53 import org.w3c.dom.NodeList;
54 import org.xml.sax.InputSource;
55 import org.xml.sax.SAXException;
56
57 /***
58 <p>Portlet which renders RDF Site Summary.</p>
59 <p>This portlet uses XML stylesheet for transforming the RSS
60 content into display markup depending on the MimeType requested
61 by the user-agent</p>
62 <p>It accepts the following parameters :
63 <dl>
64 <dt>itemDisplayed</dt>
65 <dd>The number of items from the RSS file to display on screen. Default 15 for HTML, 5 for WML</dd>
66 <dt>showDescription</dt>
67 <dd>Should the portlet show the item descriptions. Must be true or false. Default: true for HTML, false for WML</dd>
68 <dt>showTitle</dt>
69 <dd>Should the portlet show the channel description. Must be true or false. Default: true for HTML, false for WML</dd>
70 <dt>stylesheet[.<mime>]</dt>
71 <dd>The stylesheet URL. If a mime-type is specified, the stylesheet
72 is only used for this mime-type</dd>
73 </dl>
74 @author <A HREF="mailto:raphael@apache.org">Raphaël Luta</A>
75 @version $Id: NewRSSPortlet.java,v 1.22 2004/02/23 04:03:34 jford Exp $
76 */
77 public class NewRSSPortlet extends FileWatchPortlet
78 {
79
80 /***
81 * Static initialization of the logger for this class
82 */
83 private static final JetspeedLogger logger = JetspeedLogFactoryService.getLogger(NewRSSPortlet.class.getName());
84
85 public final static String ERROR_NOT_VALID = "This does not appear to be an RSS document";
86 public final static String INVALID_TYPE = "Unable to display for this browser";
87
88 private Document document = null;
89 private Hashtable stylesheets = null;
90 private Hashtable params = null;
91
92 /***
93 This method loads the init parameters and
94 parse the document tied to this portlet
95 */
96 public void init( ) throws PortletException {
97
98
99 super.init();
100
101 DocumentBuilder parser = null;
102 String url = null;
103
104
105 stylesheets = new Hashtable();
106 params = new Hashtable();
107 Iterator i = this.getPortletConfig().getInitParameterNames();
108 while (i.hasNext()) {
109 String name = (String)i.next();
110 String base = MimeType.HTML.toString();
111
112 if (name.startsWith("stylesheet")) {
113 int idx=-1;
114 if ((idx=name.indexOf("."))>-1) {
115 base= name.substring(idx+1,name.length());
116 }
117 stylesheets.put(base, getPortletConfig().getInitParameter(name));
118 } else {
119 params.put(name.toLowerCase(), getPortletConfig().getInitParameter(name));
120 }
121 }
122
123
124 try
125 {
126 final DocumentBuilderFactory docfactory = DocumentBuilderFactory.newInstance();
127
128 docfactory.setValidating(false);
129 parser= docfactory.newDocumentBuilder();
130 parser.setEntityResolver(new JetspeedXMLEntityResolver() );
131
132 url = getPortletConfig().getURL();
133 String content = JetspeedDiskCache.getInstance().getEntry( url ).getData();
134 CapabilityMap xmap =
135 CapabilityMapFactory.getCapabilityMap(CapabilityMapFactory.AGENT_XML);
136 setContent( new JetspeedClearElement(content), xmap );
137 InputSource isrc = new InputSource( this.cleanse( content ) );
138 isrc.setSystemId( url );
139 isrc.setEncoding("UTF-8");
140 this.document = parser.parse( isrc );
141 this.setMetainfo(document);
142
143 } catch ( Throwable t )
144 {
145
146 String message = "RSSPortlet: Couldn't parse out XML document -> " +
147 url;
148
149 logger.error( message, t );
150 throw new PortletException( t.getMessage() );
151 }
152
153
154 }
155
156 /***
157 * Parse out title and description
158 *
159 * @param document
160 */
161 private void setMetainfo(Document document) throws PortletException
162 {
163
164 String title = null;
165 String description = null;
166
167
168 Node channel = null;
169
170 NodeList list = document.getElementsByTagName( "channel" );
171
172 if ( list.getLength() != 1 ) {
173 throw new PortletException( ERROR_NOT_VALID );
174 }
175
176 channel = list.item( 0 );
177
178 Node tn = getNode( channel, "title" );
179
180 if ( tn == null ) {
181 throw new PortletException( ERROR_NOT_VALID );
182 }
183 else
184 {
185 Node fc = tn.getFirstChild();
186 if (fc != null)
187 {
188 title = fc.getNodeValue();
189 }
190 }
191
192 Node dn = getNode( channel, "description" );
193
194 if ( dn != null )
195 {
196 Node fc = dn.getFirstChild();
197 if (fc != null)
198 {
199 description = fc.getNodeValue();
200 }
201 }
202
203 this.setTitle( title );
204 this.setDescription( description );
205 }
206
207 /***
208 This methods outputs the content of the portlet for a given
209 request.
210
211 @param data the RunData object for the request
212 @return the content to be displayed to the user-agent
213 */
214 public ConcreteElement getContent( RunData data )
215 {
216 if (org.apache.jetspeed.util.PortletSessionState.getPortletConfigChanged(this, data))
217 {
218 try
219 {
220 init();
221 }
222 catch (PortletException pe)
223 {
224 logger.error("Exception", pe);
225 }
226 }
227 CapabilityMap map = ((JetspeedRunData)data).getCapability();
228 String type = map.getPreferredType().toString();
229 ConcreteElement content = new JetspeedClearElement(INVALID_TYPE);
230 String stylesheet = (String)stylesheets.get(type);
231
232 if (stylesheet != null) {
233 content = getContent( data, map );
234 if ( content == null ) {
235 try {
236 content = new JetspeedClearElement(
237 SimpleTransform.transform( this.document,
238 stylesheet,
239 this.params ) );
240 setContent( content, map );
241 } catch ( SAXException e ) {
242 logger.error("Exception", e);
243 content = new JetspeedClearElement(e.getMessage());
244 }
245 }
246 }
247 else
248 {
249 if (map.getPreferredType().equals(MimeType.XML))
250 {
251 return getContent( data, map );
252 }
253 }
254
255 return content;
256 }
257
258 /***
259 This portlet supports has many types as those
260 it has stylesheets defined for in its parameters
261
262 @see Portlet#supportsType
263 @param mimeType the MIME type queried
264 @return true if the portlet knows how to display
265 content for mimeType
266 */
267 public boolean supportsType( MimeType mimeType ) {
268
269 Enumeration en = stylesheets.keys();
270 while(en.hasMoreElements()) {
271 String type = (String)en.nextElement();
272 if (type.equals(mimeType.toString())) return true;
273 }
274
275 return false;
276 }
277
278 /***
279 Utility method for traversing the document parsed
280 DOM tree and retrieving a Node by tagname
281
282 @param start the parent node for the search
283 @param name the tag name to be searched for
284 @return the first child node of start whose tagname
285 is name
286 */
287 private final Node getNode( Node start, String name ) {
288
289 NodeList list = start.getChildNodes();
290
291 for ( int i = 0; i < list.getLength(); ++i ) {
292
293 Node node = list.item( i );
294
295 if ( node.getNodeName().equals( name ) ) {
296 return node;
297 }
298 }
299 return null;
300 }
301
302 /***
303 Given a URL to some content, clean the content to Xerces can handle it
304 better. Right now this involves:
305 <ul>
306 <li>
307 If the document doesn't begin with "<?xml version=" truncate the
308 content until this is the first line
309 </li>
310
311 </ul>
312
313 */
314 private Reader cleanse( String content ) throws IOException {
315
316 String filtered = null;
317
318
319
320 String XMLDECL = "<?xml version=";
321
322 int start = content.indexOf( XMLDECL );
323
324 if ( start <= 0 ) {
325 filtered = content;
326 } else {
327 filtered = content.substring( start, content.length() );
328 }
329
330 return new StringReader( filtered );
331 }
332
333 }
334