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&amp;hPoll=Sys/p/sandbox&amp;hUser=Jill-ProviderNet&amp;hStartDate=1330969452&amp;wPretty' target='_top'
039 *                          >http://reluk.ca:8080/v/wap?wCall=hHarvest&amp;hPoll=Sys/p/sandbox&amp;hUser=Jill-ProviderNet&amp;hStartDate=1330969452&amp;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}