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}