Coverage Report - yarfraw.io.CachedFeedReader
 
Classes in this File Line Coverage Branch Coverage Complexity
CachedFeedReader
64% 
100% 
0
 
 1  
 package yarfraw.io;
 2  
 
 3  
 import java.io.IOException;
 4  
 import java.io.InputStream;
 5  
 
 6  
 import javax.xml.bind.ValidationEventHandler;
 7  
 
 8  
 import org.apache.commons.httpclient.Header;
 9  
 import org.apache.commons.httpclient.HttpClient;
 10  
 import org.apache.commons.httpclient.HttpURL;
 11  
 import org.apache.commons.httpclient.methods.GetMethod;
 12  
 import org.apache.commons.httpclient.params.HttpClientParams;
 13  
 
 14  
 import yarfraw.core.datamodel.ChannelFeed;
 15  
 import yarfraw.core.datamodel.YarfrawException;
 16  
 
 17  
 /**
 18  
  * This is a special {@link FeedReader} that supports "conditional get".
 19  
  * <p>
 20  
  * When a client first asks this reader to read from a remote source, this
 21  
  * reader automatically holds a reference to the returned {@link ChannelFeed}.
 22  
  * <br/>
 23  
  * When a client asks this reader to read again from the remote source, this reader
 24  
  * first queries the server to check if there's any new changes (by checking its last
 25  
  * modified date). If there is new changes, it reads and parse the updated feed and updates
 26  
  * the cache {@link ChannelFeed}. If there is no new changes since the last read, it returns
 27  
  * the cached {@link ChannelFeed} since there's no new changes.
 28  
  * </p>
 29  
  * <p>
 30  
  * Since this reader only need to perform parsing when there's new changes, it will
 31  
  * have better performance than the normal {@link FeedReader} class.
 32  
  * </p> 
 33  
  * Note that this class is only useful for reading from a remote source. 
 34  
  * 
 35  
  * <p>
 36  
  * for more information about conditional get, see 
 37  
  * &lt;a href="http://fishbowl.pastiche.org/2002/10/21/http_conditional_get_for_rss_hackers" />
 38  
  * </p>
 39  
  * @author jliang
 40  
  *
 41  
  */
 42  
 public class CachedFeedReader extends FeedReader{
 43  
   
 44  
   private static final String ETAG = "ETag";
 45  
   private static final String LAST_MODIFIED = "Last-Modified";
 46  
   private static final int NOT_MODIFIED_STATUS_CODE = 304;
 47  
   private static final String IF_NONE_MATCH = "If-None-Match";
 48  
   private static final String IF_MODIFIED_SINCE = "If-Modified-Since";
 49  
   private ChannelFeed _cachedChannelFeed;
 50  
   private String _lastModified;
 51  
   private String _eTag;
 52  
   
 53  
   /**
 54  
    * Reads a channel from a local or remote feed.
 55  
    * <br/>
 56  
    * This method performs conditional get, if the remote is not modified
 57  
    * since last request, the cached feed will be returned.
 58  
    * 
 59  
    * @return a {@link ChannelFeed} object
 60  
    * @throws YarfrawException if read operation failed.
 61  
    */
 62  
   @Override
 63  
   public ChannelFeed readChannel() throws YarfrawException{
 64  
     try {
 65  8
       _cachedChannelFeed = super.readChannel(null);
 66  3
     } catch (NotModfiedException e) {
 67  
       //no changes since last request, no need to update cache
 68  
       //just return the cached feed
 69  5
     }
 70  8
     return _cachedChannelFeed;
 71  
   }
 72  
   
 73  
   /**
 74  
    * Reads a channel from a local or remote feed with a custom {@link ValidationEventHandler}
 75  
    *<br/>
 76  
    * This method performs conditional get, if the remote is not modified
 77  
    * since last request, the cached feed will be returned.
 78  
    * 
 79  
    * @param validationEventHandler a custom {@link javax.xml.bind.ValidationEventHandler}
 80  
    * @return a {@link ChannelFeed} object
 81  
    * @throws YarfrawException if read operation failed.
 82  
    */
 83  
   public ChannelFeed readChannel(ValidationEventHandler validationEventHandler) throws YarfrawException{
 84  
     try {
 85  0
       _cachedChannelFeed = super.readChannel(validationEventHandler);
 86  0
     } catch (NotModfiedException e) {
 87  
       //no changes since last request, no need to update cache
 88  
       //just return the cached feed
 89  0
     }
 90  0
     return _cachedChannelFeed;
 91  
   }
 92  
   //a special exception to signal there's no need changes to be parsed
 93  
   //the reader can just return the cached version
 94  3
   class NotModfiedException extends RuntimeException{
 95  
     private static final long serialVersionUID = 1L;}
 96  
   
 97  
   
 98  
   @Override
 99  
   protected InputStream getStream() throws IOException{
 100  
     InputStream stream;
 101  11
     GetMethod get = new GetMethod(_httpUrl.toString());
 102  11
     HttpClient client = new HttpClient();
 103  11
     if(_httpClientParams != null){
 104  0
       client.setParams(_httpClientParams);
 105  
     }
 106  
     
 107  
     //there's a cached version of the feed, add the request
 108  
     //header to perform conditional get
 109  11
     if(_cachedChannelFeed != null){
 110  5
       get.addRequestHeader(new Header(IF_MODIFIED_SINCE, _lastModified));
 111  5
       get.addRequestHeader(new Header(IF_NONE_MATCH, _eTag));
 112  
     }
 113  11
     client.executeMethod(get);
 114  
     //get the headers
 115  11
     Header lastModified = get.getResponseHeader(LAST_MODIFIED);
 116  11
     _lastModified = lastModified == null ? null : lastModified.getValue();
 117  11
     Header eTag = get.getResponseHeader(ETAG);
 118  11
     _eTag = eTag == null ? null : eTag.getValue();
 119  
     
 120  11
     if(get.getStatusCode()==NOT_MODIFIED_STATUS_CODE){
 121  3
       throw new NotModfiedException();
 122  
     }
 123  8
     stream = get.getResponseBodyAsStream();
 124  8
     return stream; 
 125  
   }
 126  
 
 127  
   /**
 128  
    * 
 129  
    * @return - The value of the "Last-Modified" response header from the last read.
 130  
    */
 131  
   public String getLastModified() {
 132  0
     return _lastModified;
 133  
   }
 134  
 
 135  
   /**
 136  
    * 
 137  
    * @return The value of the "ETag" response header from the last read.
 138  
    */
 139  
   public String getETag() {
 140  0
     return _eTag;
 141  
   }
 142  
 
 143  
   /**
 144  
    * Setter for the "ETag" response header. 
 145  
    * <br/>This method is used to let the caller set an initial value
 146  
    * for this header. <br/> 
 147  
    * This value will be automatically updated by this class for every
 148  
    * {@link #readChannel()} method call.
 149  
    * @param tag
 150  
    */
 151  
   public void setETag(String tag) {
 152  0
         _eTag = tag;
 153  0
   }
 154  
   
 155  
   /**
 156  
    * Setter for the "Last-Modified" response header.
 157  
    * <br/>This method is used to let the caller set an initial value
 158  
    * for this header. <br/> 
 159  
    * This value will be automatically updated by this class for every
 160  
    * {@link #readChannel()} method call.
 161  
    * @param tag
 162  
    */
 163  
   public void setLastModified(String lastModified) {
 164  0
         _lastModified = lastModified;
 165  0
   }
 166  
   
 167  
   public ChannelFeed getCachedChannelFeed() {
 168  4
     return _cachedChannelFeed;
 169  
   }
 170  
 
 171  
   /**
 172  
    * Constructs a {@link CachedFeedReader} to read from a remote source using Http.
 173  
    * <br/>
 174  
    * Format detection will be automatically performed. 
 175  
    * @param httpUrl - the {@link HttpURL} of the remote source
 176  
    * @param params - any {@link HttpClientParams}
 177  
    * @throws YarfrawException - if parse failed
 178  
    * @throws IOException - if format detection failed
 179  
    */
 180  
   @SuppressWarnings("deprecation")
 181  
   public CachedFeedReader(HttpURL httpUrl, HttpClientParams params) throws YarfrawException, IOException{
 182  0
     super(httpUrl, params);
 183  0
   }
 184  
   
 185  
   /**
 186  
    * Constructs a {@link CachedFeedReader} to read from a remote source using Http.
 187  
    * <br/>
 188  
    * Format detection will be automatically performed.
 189  
    * @param httpUrl - the {@link HttpURL} of the remote source
 190  
    * @throws YarfrawException - if parse failed
 191  
    * @throws IOException - if format detection failed
 192  
    */
 193  
   @SuppressWarnings("deprecation")
 194  
   public CachedFeedReader(HttpURL httpUrl) throws YarfrawException, IOException{
 195  3
     super(httpUrl, null);
 196  3
   }
 197  
   
 198  
 }