package 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. import com.google.gson.stream.*; import java.io.*; import java.lang.reflect.*; import java.util.*; import java.util.logging.*; import java.util.regex.*; import javax.servlet.*; import javax.servlet.http.*; import votorola.a.*; import votorola.a.web.wic.*; import votorola.g.lang.*; import votorola.g.logging.*; import votorola.g.web.*; import votorola.s.wap.*; import votorola.s.wap.store.*; /** A web API for Votorola's servlet container (Tomcat). It provides an HTTP interface * for remote application programmers. A single instance of WAP is constructed by the * container as defined in the web.xml * configuration file. The service path is /wap. An example request is: * *
http://reluk.ca:8080/v/wap?wCall=cCount&cPoll=G%2Fp%2Fsandbox&wPretty
* *

Query parameters

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
KeyValueDefault
wCallThe list of {@linkplain Call typed calls} for this request, separated by * apostrophes ('). For example, "wCall=hHarvest'psPollspace" specifies call * types Harvest and Pollspace under prefixes 'h' and 'ps' respectively. * Prefixes provide namespacing of query parameters and results that are * particular to each call type. They are restricted to lower case ASCII * characters, excepting the single letter prefix 'w' which is reserved.Null, making no calls.
wCallbackThe name of the callback function for a JSONP response, or leave it * unspecified for a plain JSON response. This is a qualifier for wForm whenever * that parameter is set to 'JSON'.Null, specifying a plain JSON response.
wFormThe form of the response. Currently only 'JSON' is supported, which gives * either JSON or JSONP depending on the value of wCallback.'JSON'
wNonceAn arbitrary value intended to give the request a unique URL. Use it to * defeat caching in clients that ignore the {@linkplain * ResponseConfiguration#headNoCache() cache control headers}.Null, optional item.
wPrettySpecify 'wPretty' or 'wPretty=y' to make the response more human readable. * This adds whitespace and forces a content type of 'text/plain'.'n'
* *

Response

* *

The response has the following general form, shown here with JSON formatting and * explanatory comments. For further details, see the API documentation of the individual * {@linkplain Call calls}.

 {
  *    "CALL-PREFIX-1": {
  *       // response to first call
  *     },
  *    "CALL-PREFIX-2": {
  *       // response to second call
  *    }
  *    // and so on, for each call
  *}
* * @see Pollwiki web APIs */ public final @ThreadSafe class WAP extends HttpServlet { public @Override void init() throws ServletException { try { vsRun = new VoteServer( VOWicket.contextPathToVoteServerName( // OPT for space: if whole vote-server really needed, servlets ought to share it getServletContext().getContextPath() )).new Run( /*isSingleThreaded*/false ); vsRun.init_done(); // nothing to do here anymore synchronized( WAP.this ) { callConstructors.put( CountWAP.CALL_TYPE, CountWAP.class ); callConstructors.put( DiffWAP.CALL_TYPE, DiffWAP.class ); callConstructors.put( StoreWAP.CALL_TYPE, StoreWAP.class ); callConstructors.put( HarvestWAP.CALL_TYPE, HarvestWAP.class ); callConstructors.put( KickWAP.CALL_TYPE, KickWAP.class ); callConstructors.put( PollspaceWAP.CALL_TYPE, PollspaceWAP.class ); } } catch( final IOException | ReflectiveOperationException | javax.script.ScriptException | java.sql.SQLException | java.net.URISyntaxException x ) { throw new ServletException( x ); } } // ------------------------------------------------------------------------------------ /** Map of constructors for all known call types, keyed by type name. */ @Warning("thread restricted object, holds WAP.this") Map> callConstructors() { return callConstructors; } // OPT: this could be made thread-safe by touch-sync and guaranteeing immutability private final ConstructorMap callConstructors = new ConstructorMap(); /** The run of the vote-server against which requests are made. */ public VoteServer.Run vsRun() { return vsRun; } private volatile VoteServer.Run vsRun; // final after init /** The pattern of a call entry in query parameter 'wCall'. Sets groups (1) call * prefix and (2) call type. */ static final Pattern W_CALL_PATTERN = Pattern.compile( "([a-vxyz]|[a-z]{2,})([A-Z][a-zA-Z_0-9]*)(?:'|$)" ); // PREFIX CALL-TYPE SEPAR // // The apostrophe separator (') was chosen because it is unreserved in a URI: // http://tools.ietf.org/html/rfc2396#section-2.3 // - H t t p - S e r v l e t ---------------------------------------------------------- protected @Override void doGet( final HttpServletRequest req, final HttpServletResponse res ) throws IOException { try { final Level level = Level.FINE; if( logger.isLoggable(level) ) { logger.log( level, HTTPServletRequestX.buildRequestSummary(req).toString() ); } new Responding( WAP.this, req, res ).respond(); } catch( final Exception x ) { if( !res.isCommitted() ) { if( x instanceof HTTPRequestException ) { res.sendError( ((HTTPRequestException)x).code(), x.toString() ); return; // client's error, no need to log it here } res.sendError( /*500*/HttpServletResponse.SC_INTERNAL_SERVER_ERROR, x.toString() ); } logger.log( LoggerX.WARNING, "Error in service of request " + req.getQueryString(), x ); } } //// P r i v a t e /////////////////////////////////////////////////////////////////////// private static final Logger logger = LoggerX.i( WAP.class ); // ==================================================================================== private final class ConstructorMap extends HashMap> { private void put( final String callType, final Class cl ) throws ReflectiveOperationException { final Object oldValue = super.put( callType, cl.getConstructor( String.class, Requesting.class, ResponseConfiguration.class )); if( oldValue != null ) throw new IllegalStateException(); // duplicate call type cl.getMethod("init",WAP.class).invoke( /*static, no object*/null, WAP.this ); } // - M a p ------------------------------------------------------------------------ public @Override Constructor put( final String callType, final Constructor call ) { throw new UnsupportedOperationException(); } } }