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 java.io.OutputStream;
005import java.io.PrintStream;
006
007import com.google.gson.stream.JsonWriter;
008import java.sql.SQLException;
009import java.util.logging.Level;
010import java.util.logging.Logger;
011
012import javax.mail.internet.AddressException;
013import javax.script.ScriptException;
014
015import votorola.g.lang.ThreadRestricted;
016import votorola.g.logging.LoggerX;
017import votorola.a.PageProperty;
018import votorola.a.PagePropertyReader;
019import votorola.a.PollwikiVS;
020import votorola.a.VoteServer;
021import votorola.a.WikiCache;
022import votorola.a.count.Vote;
023import votorola.a.diff.harvest.HarvestReporter;
024import votorola.a.diff.harvest.cache.HarvestCache;
025import votorola.a.diff.harvest.kick.Kicker;
026import votorola.a.diff.harvest.kick.UpdateKick;
027import votorola.a.voter.IDPair;
028import votorola.a.web.wap.Call;
029import votorola.a.web.wap.Requesting;
030import votorola.a.web.wap.Responding;
031import votorola.a.web.wap.WAP;
032import votorola.a.web.wap.ResponseConfiguration;
033import votorola.g.web.HTTPRequestException;
034import votorola.g.web.HTTPServletRequestX;
035
036/**
037 * A web API to kick the harvester to update forums.
038 */
039
040public @ThreadRestricted("constructor")
041final class KickWAP extends Call {
042
043    /**
044     * The name to use in the {@link WAP wCall} query parameter, which is * * *
045     * * {@value} . For example: <code>wCall=kKick</code>.
046     */
047    public static final String CALL_TYPE = "Kick";
048
049    private static final Logger LOGGER = LoggerX.i(KickWAP.class);
050
051    // error handling
052    private final static String ERROR = "Could not initialize kick WAP service.";
053    private String errorMessage;
054    private final boolean isInit; // started succesfully?
055
056    // parameters extraced from HTTP GET request
057    private final String poll;
058    private final String user;
059
060    /**
061     * Constructs a KickWAP. This service is used by
062     * {@linkplain votorola.s.gwt.stage.talk.TalkTrack} to trigger harvesters to
063     * update a forum.
064     * 
065     * @param _prefix
066     * @param _req
067     * @param _resConfig
068     * @throws HTTPRequestException
069     */
070    public KickWAP(String _prefix, Requesting _req,
071            ResponseConfiguration _resConfig) throws HTTPRequestException {
072        super(_prefix, _req, _resConfig);
073        boolean tmpInit = false;
074
075        String tempPoll = null, tempUser = null, tempMessage = null;
076        try {
077            tempPoll = HTTPServletRequestX.getParameterNonEmpty(prefix()
078                    + "Poll", req().request());
079            tempUser = HTTPServletRequestX.getParameterNonEmpty(prefix()
080                    + "User", req().request());
081
082            tmpInit = true;
083
084        } catch (Exception e) {
085            tempMessage = e.getMessage();
086            LOGGER.log(Level.WARNING, ERROR, e);
087        }
088        poll = tempPoll;
089        user = tempUser;
090        isInit = tmpInit;
091        errorMessage = tempMessage;
092    }
093
094    /**
095     * Servlet response, streaming out JSON from the
096     * {@linkplain votorola.a.diff.harvest.cache.DiffMessageTable}.
097     */
098    @Override
099    public void respond(Responding res) throws IOException {
100        // This method is also called by the default implementation of doHead(),
101        // which discards the response body after learning its length.
102        try {
103            LOGGER.fine(votorola.g.web.HTTPServletRequestX.buildRequestSummary(
104                    res.request()).toString());
105
106            final JsonWriter out = res.outJSON();
107
108            if (!isInit) {
109                error(out, errorMessage);
110                return; // init error
111            }
112
113            final VoteServer.Run vsRun = req().wap().vsRun();
114            // ensure harvest services are initialized
115            HarvestCache.init(vsRun);
116
117            final PollwikiVS wiki = vsRun.voteServer().pollwiki();
118            final WikiCache wc = wiki.cache();
119
120            final IDPair id = IDPair.fromUsername(user);
121            final Vote vote = new Vote(id, vsRun.scopePoll().ensurePoll(poll)
122                    .voterInputTable());
123            boolean triggered = false;
124            // allow endcandidates to be triggered.
125            if(vote.getCandidate().equals(IDPair.NOBODY)) {
126                triggered |= triggerUpdates(wiki.positionPageName(vote.voter().username(), poll),wc);
127            } else {
128                triggered |= triggerUpdates(wiki.positionPageName(vote.getCandidate().username(), poll),wc);
129            }
130            
131            out.name(prefix()).beginObject();
132            out.name("triggered").value(triggered);
133            out.endObject();
134
135        } catch (RuntimeException | AddressException | SQLException
136                | ScriptException x) {
137            LOGGER.log(LoggerX.WARNING, /* message */"", x);
138        }
139    }
140
141    /**
142     * Creates {@linkplain UpdateKick} for each Forum linked on the page.
143     * @param page here user positions.
144     * @param wc
145     * @return whether a forum has been found and triggered
146     * @throws IOException
147     */
148    private boolean triggerUpdates(final String page, final WikiCache wc)
149            throws IOException {
150
151        final PageProperty forumP = new PageProperty("Forum");
152        final PagePropertyReader forumR = new PagePropertyReader(wc, page,
153                forumP);
154
155        boolean triggered = false;
156        while (forumR.hasNext()) {
157            final String name = forumR.read().getValue();
158
159            final PageProperty designP = new PageProperty("Archive design");
160            final PagePropertyReader designR = new PagePropertyReader(wc, name,
161                    designP);
162
163            final PageProperty urlP = new PageProperty("Archive URL");
164            final PagePropertyReader urlR = new PagePropertyReader(wc, name,
165                    urlP);
166
167            if (designR.hasNext()) {
168                triggered = true;
169                final String design = designR.read().getValue();
170                final String url = urlR.read().getValue();
171                LOGGER.fine("Triggered " + name + " update for " + poll + ":"
172                        + user + " targeting " + design + " " + url);
173                Kicker.i().broadcast(
174                        UpdateKick.create(design, url, new HarvestReporter() {
175
176                            @Override
177                            public void proccessFinished() {
178                                // we ignore feedback
179                            }
180
181                            @Override
182                            public PrintStream printStream() {
183                                return new PrintStream(new OutputStream() {
184
185                                    @Override
186                                    public void write(int b) throws IOException {
187                                        // ignore
188                                    }
189
190                                });
191                            }
192                        }));
193            }
194        }
195        return triggered;
196    }
197
198    private void error(final JsonWriter out, final String errorMessage)
199            throws IOException {
200        out.name("error").value(errorMessage);
201    }
202
203}