001/* 002 Licensed to the Apache Software Foundation (ASF) under one 003 or more contributor license agreements. See the NOTICE file 004 distributed with this work for additional information 005 regarding copyright ownership. The ASF licenses this file 006 to you under the Apache License, Version 2.0 (the 007 "License"); you may not use this file except in compliance 008 with the License. You may obtain a copy of the License at 009 010 http://www.apache.org/licenses/LICENSE-2.0 011 012 Unless required by applicable law or agreed to in writing, 013 software distributed under the License is distributed on an 014 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 KIND, either express or implied. See the License for the 016 specific language governing permissions and limitations 017 under the License. 018 */ 019package org.apache.wiki.rss; 020 021import org.apache.log4j.Logger; 022import org.apache.wiki.api.core.Attachment; 023import org.apache.wiki.api.core.Context; 024import org.apache.wiki.api.core.ContextEnum; 025import org.apache.wiki.api.core.Engine; 026import org.apache.wiki.api.core.Page; 027import org.apache.wiki.api.core.Session; 028import org.apache.wiki.api.providers.WikiProvider; 029import org.apache.wiki.api.spi.Wiki; 030import org.apache.wiki.auth.AuthorizationManager; 031import org.apache.wiki.auth.permissions.PagePermission; 032import org.apache.wiki.diff.DifferenceManager; 033import org.apache.wiki.pages.PageManager; 034import org.apache.wiki.pages.PageTimeComparator; 035import org.apache.wiki.render.RenderingManager; 036import org.apache.wiki.util.TextUtil; 037import org.apache.wiki.variables.VariableManager; 038 039import java.io.File; 040import java.util.Iterator; 041import java.util.List; 042import java.util.Properties; 043import java.util.Set; 044 045 046/** 047 * Default implementation for {@link RSSGenerator}. 048 * 049 * {@inheritDoc} 050 */ 051// FIXME: Limit diff and page content size. 052public class DefaultRSSGenerator implements RSSGenerator { 053 054 private static final Logger log = Logger.getLogger( DefaultRSSGenerator.class ); 055 private Engine m_engine; 056 057 /** The RSS file to generate. */ 058 private String m_rssFile; 059 private String m_channelDescription = ""; 060 private String m_channelLanguage = "en-us"; 061 private boolean m_enabled = true; 062 063 private static final int MAX_CHARACTERS = Integer.MAX_VALUE-1; 064 065 /** 066 * Builds the RSS generator for a given Engine. 067 * 068 * @param engine The Engine. 069 * @param properties The properties. 070 */ 071 public DefaultRSSGenerator( final Engine engine, final Properties properties ) { 072 m_engine = engine; 073 m_channelDescription = properties.getProperty( PROP_CHANNEL_DESCRIPTION, m_channelDescription ); 074 m_channelLanguage = properties.getProperty( PROP_CHANNEL_LANGUAGE, m_channelLanguage ); 075 m_rssFile = TextUtil.getStringProperty( properties, DefaultRSSGenerator.PROP_RSSFILE, "rss.rdf" ); 076 } 077 078 /** 079 * {@inheritDoc} 080 * 081 * Start the RSS generator & generator thread 082 */ 083 @Override 084 public void initialize( final Engine engine, final Properties properties ) { 085 final File rssFile; 086 if( m_rssFile.startsWith( File.separator ) ) { // honor absolute pathnames 087 rssFile = new File( m_rssFile ); 088 } else { // relative path names are anchored from the webapp root path 089 rssFile = new File( engine.getRootPath(), m_rssFile ); 090 } 091 final int rssInterval = TextUtil.getIntegerProperty( properties, DefaultRSSGenerator.PROP_INTERVAL, 3600 ); 092 final RSSThread rssThread = new RSSThread( engine, rssFile, rssInterval ); 093 rssThread.start(); 094 } 095 096 private String getAuthor( final Page page ) { 097 String author = page.getAuthor(); 098 if( author == null ) { 099 author = "An unknown author"; 100 } 101 102 return author; 103 } 104 105 private String getAttachmentDescription( final Attachment att ) { 106 final String author = getAuthor( att ); 107 final StringBuilder sb = new StringBuilder(); 108 109 if( att.getVersion() != 1 ) { 110 sb.append( author ).append( " uploaded a new version of this attachment on " ).append( att.getLastModified() ); 111 } else { 112 sb.append( author ).append( " created this attachment on " ).append( att.getLastModified() ); 113 } 114 115 sb.append( "<br /><hr /><br />" ) 116 .append( "Parent page: <a href=\"" ) 117 .append( m_engine.getURL( ContextEnum.PAGE_VIEW.getRequestContext(), att.getParentName(), null ) ) 118 .append( "\">" ).append( att.getParentName() ).append( "</a><br />" ) 119 .append( "Info page: <a href=\"" ) 120 .append( m_engine.getURL( ContextEnum.PAGE_INFO.getRequestContext(), att.getName(), null ) ) 121 .append( "\">" ).append( att.getName() ).append( "</a>" ); 122 123 return sb.toString(); 124 } 125 126 private String getPageDescription( final Page page ) { 127 final StringBuilder buf = new StringBuilder(); 128 final String author = getAuthor( page ); 129 final Context ctx = Wiki.context().create( m_engine, page ); 130 if( page.getVersion() > 1 ) { 131 final String diff = m_engine.getManager( DifferenceManager.class ).getDiff( ctx, 132 page.getVersion() - 1, // FIXME: Will fail when non-contiguous versions 133 page.getVersion() ); 134 135 buf.append( author ).append( " changed this page on " ).append( page.getLastModified() ).append( ":<br /><hr /><br />" ); 136 buf.append( diff ); 137 } else { 138 buf.append( author ).append( " created this page on " ).append( page.getLastModified() ).append( ":<br /><hr /><br />" ); 139 buf.append( m_engine.getManager( RenderingManager.class ).getHTML( page.getName() ) ); 140 } 141 142 return buf.toString(); 143 } 144 145 private String getEntryDescription( final Page page ) { 146 final String res; 147 if( page instanceof Attachment ) { 148 res = getAttachmentDescription( (Attachment)page ); 149 } else { 150 res = getPageDescription( page ); 151 } 152 153 return res; 154 } 155 156 // FIXME: This should probably return something more intelligent 157 private String getEntryTitle( final Page page ) { 158 return page.getName() + ", version " + page.getVersion(); 159 } 160 161 /** {@inheritDoc} */ 162 @Override 163 public String generate() { 164 final Context context = Wiki.context().create( m_engine, Wiki.contents().page( m_engine, "__DUMMY" ) ); 165 context.setRequestContext( ContextEnum.PAGE_RSS.getRequestContext() ); 166 final Feed feed = new RSS10Feed( context ); 167 return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + generateFullWikiRSS( context, feed ); 168 } 169 170 /** {@inheritDoc} */ 171 @Override 172 public String generateFeed( final Context wikiContext, final List< Page > changed, final String mode, final String type ) throws IllegalArgumentException { 173 final Feed feed; 174 final String res; 175 176 if( ATOM.equals(type) ) { 177 feed = new AtomFeed( wikiContext ); 178 } else if( RSS20.equals( type ) ) { 179 feed = new RSS20Feed( wikiContext ); 180 } else { 181 feed = new RSS10Feed( wikiContext ); 182 } 183 184 feed.setMode( mode ); 185 186 if( MODE_BLOG.equals( mode ) ) { 187 res = generateBlogRSS( wikiContext, changed, feed ); 188 } else if( MODE_FULL.equals(mode) ) { 189 res = generateFullWikiRSS( wikiContext, feed ); 190 } else if( MODE_WIKI.equals(mode) ) { 191 res = generateWikiPageRSS( wikiContext, changed, feed ); 192 } else { 193 throw new IllegalArgumentException( "Invalid value for feed mode: "+mode ); 194 } 195 196 return res; 197 } 198 199 /** {@inheritDoc} */ 200 @Override 201 public synchronized boolean isEnabled() { 202 return m_enabled; 203 } 204 205 /** {@inheritDoc} */ 206 @Override 207 public synchronized void setEnabled( final boolean enabled ) { 208 m_enabled = enabled; 209 } 210 211 /** {@inheritDoc} */ 212 @Override 213 public String getRssFile() { 214 return m_rssFile; 215 } 216 217 /** {@inheritDoc} */ 218 @Override 219 public String generateFullWikiRSS( final Context wikiContext, final Feed feed ) { 220 feed.setChannelTitle( m_engine.getApplicationName() ); 221 feed.setFeedURL( m_engine.getBaseURL() ); 222 feed.setChannelLanguage( m_channelLanguage ); 223 feed.setChannelDescription( m_channelDescription ); 224 225 final Set< Page > changed = m_engine.getManager( PageManager.class ).getRecentChanges(); 226 227 final Session session = Wiki.session().guest( m_engine ); 228 int items = 0; 229 for( final Iterator< Page > i = changed.iterator(); i.hasNext() && items < 15; items++ ) { 230 final Page page = i.next(); 231 232 // Check if the anonymous user has view access to this page. 233 if( !m_engine.getManager( AuthorizationManager.class ).checkPermission(session, new PagePermission(page,PagePermission.VIEW_ACTION) ) ) { 234 // No permission, skip to the next one. 235 continue; 236 } 237 238 final String url; 239 if( page instanceof Attachment ) { 240 url = m_engine.getURL( ContextEnum.PAGE_ATTACH.getRequestContext(), page.getName(),null ); 241 } else { 242 url = m_engine.getURL( ContextEnum.PAGE_VIEW.getRequestContext(), page.getName(), null ); 243 } 244 245 final Entry e = new Entry(); 246 e.setPage( page ); 247 e.setURL( url ); 248 e.setTitle( page.getName() ); 249 e.setContent( getEntryDescription(page) ); 250 e.setAuthor( getAuthor(page) ); 251 252 feed.addEntry( e ); 253 } 254 255 return feed.getString(); 256 } 257 258 /** {@inheritDoc} */ 259 @Override 260 public String generateWikiPageRSS( final Context wikiContext, final List< Page > changed, final Feed feed ) { 261 feed.setChannelTitle( m_engine.getApplicationName()+": "+wikiContext.getPage().getName() ); 262 feed.setFeedURL( wikiContext.getViewURL( wikiContext.getPage().getName() ) ); 263 final String language = m_engine.getManager( VariableManager.class ).getVariable( wikiContext, PROP_CHANNEL_LANGUAGE ); 264 265 if( language != null ) { 266 feed.setChannelLanguage( language ); 267 } else { 268 feed.setChannelLanguage( m_channelLanguage ); 269 } 270 final String channelDescription = m_engine.getManager( VariableManager.class ).getVariable( wikiContext, PROP_CHANNEL_DESCRIPTION ); 271 272 if( channelDescription != null ) { 273 feed.setChannelDescription( channelDescription ); 274 } 275 276 changed.sort( new PageTimeComparator() ); 277 278 int items = 0; 279 for( final Iterator< Page > i = changed.iterator(); i.hasNext() && items < 15; items++ ) { 280 final Page page = i.next(); 281 final Entry e = new Entry(); 282 e.setPage( page ); 283 String url; 284 285 if( page instanceof Attachment ) { 286 url = m_engine.getURL( ContextEnum.PAGE_ATTACH.getRequestContext(), page.getName(), "version=" + page.getVersion() ); 287 } else { 288 url = m_engine.getURL( ContextEnum.PAGE_VIEW.getRequestContext(), page.getName(), "version=" + page.getVersion() ); 289 } 290 291 // Unfortunately, this is needed because the code will again go through replacement conversion 292 url = TextUtil.replaceString( url, "&", "&" ); 293 e.setURL( url ); 294 e.setTitle( getEntryTitle(page) ); 295 e.setContent( getEntryDescription(page) ); 296 e.setAuthor( getAuthor(page) ); 297 298 feed.addEntry( e ); 299 } 300 301 return feed.getString(); 302 } 303 304 305 /** {@inheritDoc} */ 306 @Override 307 public String generateBlogRSS( final Context wikiContext, final List< Page > changed, final Feed feed ) { 308 if( log.isDebugEnabled() ) { 309 log.debug( "Generating RSS for blog, size=" + changed.size() ); 310 } 311 312 final String ctitle = m_engine.getManager( VariableManager.class ).getVariable( wikiContext, PROP_CHANNEL_TITLE ); 313 if( ctitle != null ) { 314 feed.setChannelTitle( ctitle ); 315 } else { 316 feed.setChannelTitle( m_engine.getApplicationName() + ":" + wikiContext.getPage().getName() ); 317 } 318 319 feed.setFeedURL( wikiContext.getViewURL( wikiContext.getPage().getName() ) ); 320 321 final String language = m_engine.getManager( VariableManager.class ).getVariable( wikiContext, PROP_CHANNEL_LANGUAGE ); 322 if( language != null ) { 323 feed.setChannelLanguage( language ); 324 } else { 325 feed.setChannelLanguage( m_channelLanguage ); 326 } 327 328 final String channelDescription = m_engine.getManager( VariableManager.class ).getVariable( wikiContext, PROP_CHANNEL_DESCRIPTION ); 329 if( channelDescription != null ) { 330 feed.setChannelDescription( channelDescription ); 331 } 332 333 changed.sort( new PageTimeComparator() ); 334 335 int items = 0; 336 for( final Iterator< Page > i = changed.iterator(); i.hasNext() && items < 15; items++ ) { 337 final Page page = i.next(); 338 final Entry e = new Entry(); 339 e.setPage( page ); 340 final String url; 341 342 if( page instanceof Attachment ) { 343 url = m_engine.getURL( ContextEnum.PAGE_ATTACH.getRequestContext(), page.getName(),null ); 344 } else { 345 url = m_engine.getURL( ContextEnum.PAGE_VIEW.getRequestContext(), page.getName(),null ); 346 } 347 348 e.setURL( url ); 349 350 // Title 351 String pageText = m_engine.getManager( PageManager.class ).getPureText( page.getName(), WikiProvider.LATEST_VERSION ); 352 353 String title = ""; 354 final int firstLine = pageText.indexOf('\n'); 355 356 if( firstLine > 0 ) { 357 title = pageText.substring( 0, firstLine ).trim(); 358 } 359 360 if( title.length() == 0 ) { 361 title = page.getName(); 362 } 363 364 // Remove wiki formatting 365 while( title.startsWith("!") ) { 366 title = title.substring(1); 367 } 368 369 e.setTitle( title ); 370 371 // Description 372 if( firstLine > 0 ) { 373 int maxlen = pageText.length(); 374 if( maxlen > MAX_CHARACTERS ) { 375 maxlen = MAX_CHARACTERS; 376 } 377 pageText = m_engine.getManager( RenderingManager.class ).textToHTML( wikiContext, pageText.substring( firstLine + 1, maxlen ).trim() ); 378 if( maxlen == MAX_CHARACTERS ) { 379 pageText += "..."; 380 } 381 e.setContent( pageText ); 382 } else { 383 e.setContent( title ); 384 } 385 e.setAuthor( getAuthor(page) ); 386 feed.addEntry( e ); 387 } 388 389 return feed.getString(); 390 } 391 392}