001package votorola.s.wap; // Copyright 2012. Christian Weilbach. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Votorola Software"), to deal in the Votorola Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of the Votorola Software, and to permit persons to whom the Votorola Software is furnished to do so, subject to the following conditions: The preceding copyright notice and this permission notice shall be included in all copies or substantial portions of the Votorola Software. THE VOTOROLA SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE VOTOROLA SOFTWARE OR THE USE OR OTHER DEALINGS IN THE VOTOROLA SOFTWARE. 002 003import java.io.IOException; 004import com.google.gson.stream.JsonWriter; 005import java.util.Date; 006import java.sql.ResultSet; 007import java.sql.SQLException; 008import java.util.logging.Level; 009import java.util.logging.Logger; 010 011import javax.mail.internet.AddressException; 012 013import votorola.g.lang.ThreadRestricted; 014import votorola.g.logging.LoggerX; 015import votorola.a.diff.harvest.cache.HarvestCache; 016import votorola.a.diff.DiffKeyParse; 017import votorola.a.voter.IDPair; 018import votorola.a.web.wap.Call; 019import votorola.a.web.wap.Requesting; 020import votorola.a.web.wap.Responding; 021import votorola.a.web.wap.WAP; 022import votorola.a.web.wap.ResponseConfiguration; 023import votorola.g.web.HTTPRequestException; 024import votorola.g.web.HTTPServletRequestX; 025import votorola.s.gwt.scene.feed.ss.BiteJig; 026import votorola.s.gwt.scene.feed.ss.Biter; 027import votorola.s.gwt.scene.feed.ss.DiffLookJig; 028import votorola.s.gwt.scene.feed.ss.MessageJig; 029import votorola.s.gwt.scene.feed.ss.PersonJig; 030import votorola.s.gwt.scene.feed.ss.PollJig; 031 032/** 033 * A web API for the harvested messages. An example request is: 034 * 035 * <blockquote> 036 * 037 * <pre> 038 * <a href='http://reluk.ca:8080/v/wap?wCall=hHarvest&hPoll=Sys/p/sandbox&hUser=Jill-ProviderNet&hStartDate=1330969452&wPretty' target='_top' 039 * >http://reluk.ca:8080/v/wap?wCall=hHarvest&hPoll=Sys/p/sandbox&hUser=Jill-ProviderNet&hStartDate=1330969452&wPretty</a> 040 * </pre> 041 * 042 * </blockquote> 043 * 044 * <h2>Query parameters</h2> 045 * 046 * <p> 047 * These are the specific parameters for the cache engine API. See also the 048 * general parameters for the {@linkplain WAP web API}. The examples shown below 049 * assume a call prefix of 'h', but the actual prefix depends on the 050 * {@linkplain WAP wCall} request parameter. Newest are first in the returned 051 * JSON Array. 052 * </p> 053 * 054 * <table class='definition' style='margin-left:1em'> 055 * <tr> 056 * <th class='key'>Key</th> 057 * <th>Value</th> 058 * <th>Default</th> 059 * </tr> 060 * 061 * <tr> 062 * <td class='key'>hPoll</td> 063 * <td>Names the <a href='http://reluk.ca/w/Category:Poll' 064 * target='_top'>poll</a>.</td> 065 * <td>Empty, optional item.</td> 066 * </tr> 067 * 068 * <tr> 069 * <td class='key'>hUser</td> 070 * <td>Specify 'hUser=Jim-MailCom' to request messages from a specific user(s).</td> 071 * <td>Empty, optional item.</td> 072 * </tr> 073 * 074 * <tr> 075 * <td class='key'>hId</td> 076 * <td>Specify 'hId=1234' to only request messages newer than this serial. This 077 * allows you to update a list by only fetching newer slices.</td> 078 * <td>Empty, optional item.</td> 079 * 080 * <tr> 081 * </table> 082 * 083 * <h2>Response</h2> 084 * 085 * <p> 086 * The response includes the following components. These are shown in JSON 087 * format, with explanatory comments: 088 * </p> 089 * 090 * <pre> 091 * { 092 * "{@linkplain votorola.s.gwt.scene.feed.BiteJS bite}": [ 093 * // bites in order of sent date, newest first 094 * { // {@linkplain votorola.a.diff.harvest.cache.DiffMessage diff messages} 095 * "{@linkplain votorola.a.diff.DiffLook difference}": { 096 * "key": KEY 097 * }, 098 * "{@linkplain votorola.a.diff.harvest.Message message}": { 099 * "content": "SUMMARY", 100 * "location": "WEB-URL", 101 * "sentDate": "SENT-DATE"; 102 * } 103 * "poll": "POLLNAME" 104 * "{@linkplain votorola.s.gwt.scene.feed.PersonJS persons}": [ 105 * { 106 * ... 107 * "username": "VOTERMAILISHUSERNAME", 108 * ... 109 * }, 110 * { 111 * ... 112 * "username": "CANDIDATEMAILISHUSERNAME", 113 * ... 114 * } 115 * ], 116 * }, 117 * // and so on, up to maximum server side provided messages or <a href='#hsize'>hSize</a> 118 * ] 119 * } 120 * </pre> 121 */ 122 123public @ThreadRestricted("constructor") 124final class DiffFeedWAP extends Call { 125 126 /** 127 * The name to use in the {@link WAP wCall} query parameter, which is * * 128 * {@value} . For example: <code>wCall=hHarvest</code>. 129 */ 130 public static final String CALL_TYPE = "DiffFeed"; 131 132 private static final Logger LOGGER = LoggerX.i(DiffFeedWAP.class); 133 134 // error tracking 135 private final static String ERROR = "Could not initialize diff-feed WAP service."; 136 private final boolean isInit; 137 private final String errorMessage; 138 139 // params parsed from HTTP-GET request 140 private final String poll; 141 private final String[] users; 142 private final int id; 143 144 /** 145 * Constructs a DiffFeedWAP (former HarvestWAP). 146 * 147 * @param _prefix 148 * @param _req 149 * @param _resConfig 150 * @throws HTTPRequestException 151 */ 152 public DiffFeedWAP(String _prefix, Requesting _req, 153 ResponseConfiguration _resConfig) throws HTTPRequestException { 154 super(_prefix, _req, _resConfig); 155 boolean tmpInit = false; 156 157 int tempId = 0; 158 String tempPoll = null; 159 String tempUsers = null; 160 String tempMessage = null; 161 try { 162 tempPoll = HTTPServletRequestX.getParameterNonEmpty(prefix() 163 + "Poll", req().request()); 164 tempUsers = HTTPServletRequestX.getParameterNonEmpty(prefix() 165 + "Users", req().request()); 166 final String preId = HTTPServletRequestX.getParameterNonEmpty( 167 prefix() + "Id", req().request()); 168 tempId = preId == null ? 0 : Integer.valueOf(preId); 169 170 tmpInit = true; 171 172 } catch (Exception e) { 173 tempMessage = e.getMessage(); 174 LOGGER.log(Level.WARNING, ERROR, e); 175 } 176 poll = tempPoll; 177 users = tempUsers.split(","); 178 id = tempId; 179 isInit = tmpInit; 180 errorMessage = tempMessage; 181 } 182 183 /** 184 * Method implementing the servlet response. 185 */ 186 @Override 187 public void respond(Responding res) throws IOException { 188 // This method is also called by the default implementation of doHead(), 189 // which discards the response body after learning its length. 190 try { 191 LOGGER.fine(votorola.g.web.HTTPServletRequestX.buildRequestSummary( 192 res.request()).toString()); 193 194 final JsonWriter out = res.outJSON(); 195 196 if (!isInit) { 197 error(out, errorMessage); 198 return; // init error 199 } 200 201 // synchronization to ensure the statement is not reused while we 202 // still use 203 // the result set. this is only for prototyping and very slow 204 synchronized (req().wap().vsRun().database()) { 205 ResultSet rs = HarvestCache.init(req().wap().vsRun()) 206 .getTable().get(poll, users, id); 207 208 out.name(prefix()).beginArray(); 209 210 final Biter[] biters = Biter.U.assembleBiters(req().wap() 211 .vsRun()); 212 final BiteJig bite = new BiteJig(); 213 214 // id, sender, addressee, pollname, content, base_url, path, 215 // sent_ts, a, ar, b, br, selectand 216 while (rs.next()) { 217 // Configure bite properties 218 final IDPair authorIDPair = IDPair.fromEmail(rs 219 .getString(2)); 220 final PersonJig author = bite.addPerson(); 221 author.setUser(authorIDPair); 222 223 final IDPair addresseeIDPair = IDPair.fromEmail(rs 224 .getString(3)); 225 final PersonJig addressee = bite.addPerson(); 226 addressee.setUser(addresseeIDPair); 227 228 final PollJig poll = bite.setPoll(); 229 poll.setName(rs.getString(4)); 230 231 final MessageJig msg = bite.addMessage(); 232 msg.setContent(rs.getString(5)); 233 msg.setLocation(rs.getString(6) + rs.getString(7)); 234 235 bite.setSentDate(new Date(rs.getTimestamp(8).getTime())); 236 237 // temporary up-to-date fetch-based calculation for lack of 238 // historical vote data (static relations on sent_ts) 239 bite.setRelation(HarvestCache 240 .i() 241 .relation(rs.getString(2), rs.getString(3), 242 rs.getString(4)).symbol()); 243 244 final DiffLookJig look = bite.setDifference(); 245 look.setKey(new DiffKeyParse(rs.getInt(9), rs.getInt(10), // CWFIX obsolete form 246 rs.getInt(11), rs.getInt(12)).key()); 247 248 // Maybe configure other properties, too 249 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 250 // - - 251 // - - - - 252 for (Biter biter : biters) 253 biter.configure(bite); 254 255 // Write configuration on wire 256 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 257 // - - 258 // - - - - 259 bite.serialize(out); // clears bite, ready for 260 // re-configuration 261 } 262 263 rs.close(); 264 } 265 266 out.endArray(); 267 } catch (RuntimeException | SQLException | AddressException x) { 268 LOGGER.log(LoggerX.WARNING, /* message */"", x); 269 } 270 } 271 272 private void error(final JsonWriter out, final String errorMessage) 273 throws IOException { 274 out.name("error").value(errorMessage); 275 } 276 277}