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 * <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
68
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
88
89 }
90 return _cachedChannelFeed;
91 }
92
93
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
108
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
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 }