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