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.sql.ResultSet; 006import java.sql.SQLException; 007import java.util.logging.Level; 008import java.util.logging.Logger; 009 010import javax.mail.internet.AddressException; 011 012import votorola.g.lang.ThreadRestricted; 013import votorola.g.logging.LoggerX; 014import votorola.a.diff.DiffKeyParse; 015import votorola.a.diff.harvest.cache.HarvestCache; 016import votorola.a.voter.IDPair; 017import votorola.a.web.wap.Call; 018import votorola.a.web.wap.Requesting; 019import votorola.a.web.wap.Responding; 020import votorola.a.web.wap.WAP; 021import votorola.a.web.wap.ResponseConfiguration; 022import votorola.g.web.HTTPRequestException; 023import votorola.g.web.HTTPServletRequestX; 024 025/** 026 * A web API for the harvested messages. An example request is: 027 * 028 * <blockquote> 029 * 030 * <pre> 031 * <a href='http://reluk.ca:8080/v/wap?wCall=hHarvest&hPoll=Sys/p/sandbox&hUser=Jill-ProviderNet&hStartDate=1330969452&wPretty' target='_top'>http://reluk.ca:8080/v/wap?wCall=hHarvest&hPoll=Sys/p/sandbox&hUser=Jill-ProviderNet&hStartDate=1330969452&wPretty</a> 032 * </pre> 033 * 034 * </blockquote> 035 * 036 * <h2>Query parameters</h2> 037 * 038 * <p> 039 * These are the specific parameters for the harvest cache API. See also the 040 * general parameters for the {@linkplain WAP web API}. The examples shown below 041 * assume a call prefix of 'h', but the actual prefix depends on the 042 * {@linkplain WAP wCall} request parameter. Newest are first in the returned 043 * JSON Array. 044 * </p> 045 * 046 * <table class='definition' style='margin-left:1em'> 047 * <tr> 048 * <th class='key'>Key</th> 049 * <th>Value</th> 050 * <th>Default</th> 051 * </tr> 052 * <tr> 053 * <td class='key'>hPoll</td> 054 * 055 * <td>Names the <a href='http://reluk.ca/w/Category:Poll' target='_top'>poll</a>.</td> 056 * 057 * <td>Empty, optional item.</td> 058 * </tr> 059 * <tr> 060 * <td class='key'>hUser</td> 061 * 062 * <td>Specify 'hUser=Jim-MailCom' to request messages from a specific user(s).</td> 063 * 064 * <td>Empty, optional item.</td> 065 * </tr> 066 * 067 * <tr> 068 * <td class='key'>hId</td> 069 * 070 * <td>Specify 'hId=1234' to only request messages newer than this serial. This 071 * allows you to update a list by only fetching newer slices.</td> 072 * 073 * <td>Empty, optional item.</td> 074 * <tr> 075 * </table> 076 * 077 * <h2>Response</h2> 078 * 079 * <p> 080 * The response includes the following components. These are shown in JSON 081 * format, with explanatory comments: 082 * </p> 083 * 084 * <pre> 085 * { 086 * "h": [ 087 * { 088 * "id": 16, 089 * "sender": "Mike-ZeleaCom", 090 * "message": { 091 * "content": "At the bottom: (That's our new hack/prototype of a difference bridge. It's ugly as sin. It works poorly on these drafts too, because of the long wrapped ", 092 * "location": "http://mail.zelea.com/list/votorola/2010-May/000369.html" 093 * }, 094 * "difference": { 095 * "key": "2891.2893", 096 * "selectand": "a" 097 * } 098 * }, 099 * } 100 * </pre> 101 */ 102 103public @ThreadRestricted("constructor") 104final class HarvestWAP extends Call { 105 106 /** 107 * The name to use in the {@link WAP wCall} query parameter, which is * * * 108 * * {@value} . For example: <code>wCall=hHarvest</code>. 109 */ 110 public static final String CALL_TYPE = "Harvest"; 111 112 private static final Logger LOGGER = LoggerX.i(HarvestWAP.class); 113 114 // error handling 115 private final static String ERROR = "Could not initialize harvest WAP service."; 116 private String errorMessage; 117 private final boolean isInit; // started successfully? 118 119 // parameters extracted from HTTP GET request 120 private final String poll; 121 private final String[] users; 122 private final int id; 123 124 /** 125 * Constructs a HarvestWAP. This service is used by 126 * {@linkplain votorola.s.gwt.stage.talk.TalkTrack} 127 * 128 * @param _prefix 129 * @param _req 130 * @param _resConfig 131 * @throws HTTPRequestException 132 */ 133 public HarvestWAP(String _prefix, Requesting _req, 134 ResponseConfiguration _resConfig) throws HTTPRequestException { 135 super(_prefix, _req, _resConfig); 136 boolean tmpInit = false; 137 138 int tempId = 0; 139 String tempPoll = null; 140 String tempUsers = null; 141 String tempMessage = null; 142 143 try { 144 tempPoll = HTTPServletRequestX.getParameterNonEmpty(prefix() 145 + "Poll", req().request()); 146 tempUsers = HTTPServletRequestX.getParameterNonEmpty(prefix() 147 + "Users", req().request()); 148 final String preId = HTTPServletRequestX.getParameterNonEmpty( 149 prefix() + "Id", req().request()); 150 tempId = preId == null ? 0 : Integer.valueOf(preId); 151 152 tmpInit = true; 153 154 } catch (Exception e) { 155 tempMessage = e.getMessage(); 156 LOGGER.log(Level.WARNING, ERROR, e); 157 } 158 poll = tempPoll; 159 users = (tempUsers != null) ? tempUsers.split(",") : null; 160 id = tempId; 161 isInit = tmpInit; 162 errorMessage = tempMessage; 163 } 164 165 /** 166 * Servlet response, streaming out JSON from the 167 * {@linkplain votorola.a.diff.harvest.cache.DiffMessageTable}. 168 */ 169 @Override 170 public void respond(Responding res) throws IOException { 171 // This method is also called by the default implementation of doHead(), 172 // which discards the response body after learning its length. 173 try { 174 // LOGGER.fine(votorola.g.web.HTTPServletRequestX.buildRequestSummary( 175 // res.request()).toString()); 176 //// WAP.doGet already fine-logs request, echoing same info clutters log, CWFIX 177 178 final JsonWriter out = res.outJSON(); 179 180 if (!isInit) { 181 error(out, errorMessage); 182 return; // init error 183 } 184 185 // synchronization to ensure the statement is not reused while we 186 // still use 187 // the result set. this is only for prototyping and very slow 188 synchronized (req().wap().vsRun().database()) { 189 ResultSet rs = null; 190 try { 191 rs = HarvestCache.init(req().wap().vsRun()).getTable() 192 .get(poll, users, id); 193 } catch (AddressException | SQLException e) { 194 LOGGER.log(Level.WARNING, "Cannot fetch ResultSet. ", e); 195 error(out, e.getLocalizedMessage()); 196 return; 197 } 198 199 // start regular output 200 out.name(prefix()).beginArray(); 201 202 while (rs.next()) { 203 out.beginObject(); 204 205 // id, sender, addressee, pollname, content, base_url, path, 206 // sent_ts, a, ar, b, br, selectand 207 out.name("id").value(rs.getInt(1)); 208 // out.name("pollname").value(rs.getString(4)); 209 out.name("sender") 210 .value(IDPair.toUsername(rs.getString(2))); 211 out.name("addressee") 212 .value(IDPair.toUsername(rs.getString(3))); 213 out.name("sentDate").value(rs.getTimestamp(8).getTime()); 214 215 // message 216 out.name("message").beginObject(); 217 out.name("content").value(rs.getString(5)); 218 out.name("location").value(rs.getString(6)/* base_url */ 219 + rs.getString(7)/* path */); 220 out.endObject(); 221 222 // difference 223 out.name("difference").beginObject(); 224 out.name("key").value(new DiffKeyParse(rs.getInt(9), // CWFIX obsolete form 225 rs.getInt(10), rs.getInt(11), rs.getInt(12)).key()); 226 out.name("selectand").value(rs.getString(13)); 227 out.endObject(); 228 /* out.name("sent_ts").value(rs.getTimestamp(12).getTime()); */ 229 230 // bite.setRelation(HarvestCache.i().relation(rs.getString(2),rs.getString(3),rs.getString(4)).symbol()); 231 232 out.endObject(); 233 } 234 rs.close(); 235 236 out.endArray(); 237 } 238 } catch (RuntimeException | SQLException x) { 239 LOGGER.log(LoggerX.WARNING, /* message */"", x); 240 } 241 } 242 243 private void error(final JsonWriter out, final String errorMessage) 244 throws IOException { 245 out.name("error").value(errorMessage); 246 } 247 248}