View Javadoc

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        _cachedChannelFeed = super.readChannel(null);
66      } catch (NotModfiedException e) {
67        //no changes since last request, no need to update cache
68        //just return the cached feed
69      }
70      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        _cachedChannelFeed = super.readChannel(validationEventHandler);
86      } catch (NotModfiedException e) {
87        //no changes since last request, no need to update cache
88        //just return the cached feed
89      }
90      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    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     GetMethod get = new GetMethod(_httpUrl.toString());
102     HttpClient client = new HttpClient();
103     if(_httpClientParams != null){
104       client.setParams(_httpClientParams);
105     }
106     
107     //there's a cached version of the feed, add the request
108     //header to perform conditional get
109     if(_cachedChannelFeed != null){
110       get.addRequestHeader(new Header(IF_MODIFIED_SINCE, _lastModified));
111       get.addRequestHeader(new Header(IF_NONE_MATCH, _eTag));
112     }
113     client.executeMethod(get);
114     //get the headers
115     Header lastModified = get.getResponseHeader(LAST_MODIFIED);
116     _lastModified = lastModified == null ? null : lastModified.getValue();
117     Header eTag = get.getResponseHeader(ETAG);
118     _eTag = eTag == null ? null : eTag.getValue();
119     
120     if(get.getStatusCode()==NOT_MODIFIED_STATUS_CODE){
121       throw new NotModfiedException();
122     }
123     stream = get.getResponseBodyAsStream();
124     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     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     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 	_eTag = tag;
153   }
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 	_lastModified = lastModified;
165   }
166   
167   public ChannelFeed getCachedChannelFeed() {
168     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     super(httpUrl, params);
183   }
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     super(httpUrl, null);
196   }
197   
198 }