001package votorola.a.web.wap; // Copyright 2011-2013, Michael Allan. 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 com.google.gson.stream.*; 004import java.io.*; 005import java.lang.reflect.*; 006import java.util.*; 007import java.util.logging.*; 008import java.util.regex.*; 009import javax.servlet.*; 010import javax.servlet.http.*; 011import votorola.a.*; 012import votorola.a.web.wic.*; 013import votorola.g.lang.*; 014import votorola.g.logging.*; 015import votorola.g.web.*; 016import votorola.s.wap.*; 017import votorola.s.wap.store.*; 018 019 020/** A web API for Votorola's servlet container (Tomcat). It provides an HTTP interface 021 * for remote application programmers. A single instance of WAP is constructed by the 022 * container as defined in the <code><a 023 * href='../../../../../../../a/web/context/WEB-INF/web.xml'>web.xml</a></code> 024 * configuration file. The service path is <code>/wap</code>. An example request is: 025 * 026 * <blockquote><pre><a href='http://reluk.ca:8080/v/wap?wCall=cCount&cPoll=G%2Fp%2Fsandbox&wPretty' 027 * >http://reluk.ca:8080/v/wap?wCall=cCount&cPoll=G%2Fp%2Fsandbox&wPretty</a></pre></blockquote> 028 * 029 * <h3>Query parameters</h3> 030 * 031 * <table class='definition' style='margin-left:1em'> 032 * <tr> 033 * <th class='key'>Key</th> 034 * <th>Value</th> 035 * <th>Default</th> 036 * </tr> 037 * <tr><td class='key'>wCall</td> 038 * 039 * <td>The list of {@linkplain Call typed calls} for this request, separated by 040 * apostrophes ('). For example, "wCall=hHarvest'psPollspace" specifies call 041 * types Harvest and Pollspace under prefixes 'h' and 'ps' respectively. 042 * Prefixes provide namespacing of query parameters and results that are 043 * particular to each call type. They are restricted to lower case ASCII 044 * characters, excepting the single letter prefix 'w' which is reserved.</td> 045 * 046 * <td>Null, making no calls.</td> 047 * 048 * </tr> 049 * <tr><td class='key'>wCallback</td> 050 * 051 * <td>The name of the callback function for a JSONP response, or leave it 052 * unspecified for a plain JSON response. This is a qualifier for wForm whenever 053 * that parameter is set to 'JSON'.</td> 054 * 055 * <td>Null, specifying a plain JSON response.</td> 056 * 057 * </tr> 058 * <tr><td class='key'>wForm</td> 059 * 060 * <td>The form of the response. Currently only 'JSON' is supported, which gives 061 * either JSON or JSONP depending on the value of wCallback.</td> 062 * 063 * <td>'JSON'</td> 064 * 065 * </tr> 066 * <tr id='wNonce'><td class='key'>wNonce</td> 067 * 068 * <td>An arbitrary value intended to give the request a unique URL. Use it to 069 * defeat caching in clients that ignore the {@linkplain 070 * ResponseConfiguration#headNoCache() cache control headers}.</td> 071 * 072 * <td>Null, optional item.</td> 073 * 074 * </tr> 075 * <tr><td class='key'>wPretty</td> 076 * 077 * <td>Specify 'wPretty' or 'wPretty=y' to make the response more human readable. 078 * This adds whitespace and forces a content type of 'text/plain'.</td> 079 * 080 * <td>'n'</td> 081 * </tr> 082 * </table> 083 * 084 * <h3>Response</h3> 085 * 086 * <p>The response has the following general form, shown here with JSON formatting and 087 * explanatory comments. For further details, see the API documentation of the individual 088 * {@linkplain Call calls}.</p><pre 089 * 090 *> { 091 * "CALL-PREFIX-1": { 092 * // response to first call 093 * }, 094 * "CALL-PREFIX-2": { 095 * // response to second call 096 * } 097 * // and so on, for each call 098 *}</pre> 099 * 100 * @see <a href='http://reluk.ca/w/Stuff:Pollwiki' target='_top'>Pollwiki web APIs</a> 101 */ 102public final @ThreadSafe class WAP extends HttpServlet 103{ 104 105 106 public @Override void init() throws ServletException 107 { 108 try 109 { 110 vsRun = new VoteServer( VOWicket.contextPathToVoteServerName( // OPT for space: if whole vote-server really needed, servlets ought to share it 111 getServletContext().getContextPath() )).new Run( /*isSingleThreaded*/false ); 112 vsRun.init_done(); // nothing to do here anymore 113 114 synchronized( WAP.this ) 115 { 116 callConstructors.put( CountWAP.CALL_TYPE, CountWAP.class ); 117 callConstructors.put( DiffWAP.CALL_TYPE, DiffWAP.class ); 118 callConstructors.put( StoreWAP.CALL_TYPE, StoreWAP.class ); 119 callConstructors.put( HarvestWAP.CALL_TYPE, HarvestWAP.class ); 120 callConstructors.put( KickWAP.CALL_TYPE, KickWAP.class ); 121 callConstructors.put( PollspaceWAP.CALL_TYPE, PollspaceWAP.class ); 122 } 123 } 124 catch( final IOException | ReflectiveOperationException | javax.script.ScriptException 125 | java.sql.SQLException | java.net.URISyntaxException x ) 126 { 127 throw new ServletException( x ); 128 } 129 } 130 131 132 133 // ------------------------------------------------------------------------------------ 134 135 136 /** Map of constructors for all known call types, keyed by type name. 137 */ 138 @Warning("thread restricted object, holds WAP.this") 139 Map<String,Constructor<? extends Call>> callConstructors() { return callConstructors; } 140 // OPT: this could be made thread-safe by touch-sync and guaranteeing immutability 141 142 143 private final ConstructorMap callConstructors = new ConstructorMap(); 144 145 146 147 /** The run of the vote-server against which requests are made. 148 */ 149 public VoteServer.Run vsRun() { return vsRun; } 150 151 152 private volatile VoteServer.Run vsRun; // final after init 153 154 155 156 /** The pattern of a call entry in query parameter 'wCall'. Sets groups (1) call 157 * prefix and (2) call type. 158 */ 159 static final Pattern W_CALL_PATTERN = Pattern.compile( 160 "([a-vxyz]|[a-z]{2,})([A-Z][a-zA-Z_0-9]*)(?:'|$)" ); 161 // PREFIX CALL-TYPE SEPAR 162 // 163 // The apostrophe separator (') was chosen because it is unreserved in a URI: 164 // http://tools.ietf.org/html/rfc2396#section-2.3 165 166 167 168 // - H t t p - S e r v l e t ---------------------------------------------------------- 169 170 171 protected @Override void doGet( final HttpServletRequest req, final HttpServletResponse res ) 172 throws IOException 173 { 174 try 175 { 176 final Level level = Level.FINE; 177 if( logger.isLoggable(level) ) 178 { 179 logger.log( level, HTTPServletRequestX.buildRequestSummary(req).toString() ); 180 } 181 new Responding( WAP.this, req, res ).respond(); 182 } 183 catch( final Exception x ) 184 { 185 if( !res.isCommitted() ) 186 { 187 if( x instanceof HTTPRequestException ) 188 { 189 res.sendError( ((HTTPRequestException)x).code(), x.toString() ); 190 return; // client's error, no need to log it here 191 } 192 193 res.sendError( /*500*/HttpServletResponse.SC_INTERNAL_SERVER_ERROR, x.toString() ); 194 } 195 logger.log( LoggerX.WARNING, "Error in service of request " + req.getQueryString(), x ); 196 } 197 } 198 199 200 201//// P r i v a t e /////////////////////////////////////////////////////////////////////// 202 203 204 private static final Logger logger = LoggerX.i( WAP.class ); 205 206 207 208 // ==================================================================================== 209 210 211 private final class ConstructorMap extends HashMap<String,Constructor<? extends Call>> 212 { 213 214 private void put( final String callType, final Class<? extends Call> cl ) 215 throws ReflectiveOperationException 216 { 217 final Object oldValue = super.put( callType, cl.getConstructor( 218 String.class, Requesting.class, ResponseConfiguration.class )); 219 if( oldValue != null ) throw new IllegalStateException(); // duplicate call type 220 221 cl.getMethod("init",WAP.class).invoke( /*static, no object*/null, WAP.this ); 222 } 223 224 225 // - M a p ------------------------------------------------------------------------ 226 227 228 public @Override Constructor<? extends Call> put( final String callType, 229 final Constructor<? extends Call> call ) 230 { 231 throw new UnsupportedOperationException(); 232 } 233 234 } 235 236 237}