001package votorola.s.wap; // Copyright 2011, 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.util.*; 006import java.util.logging.*; 007import javax.servlet.*; 008import javax.servlet.http.*; 009import votorola.a.*; 010import votorola.a.voter.*; 011import votorola.a.web.wic.*; 012import votorola.g.lang.*; 013import votorola.g.logging.*; 014import votorola.g.web.*; 015import votorola.s.gwt.scene.dum.*; 016import votorola.s.gwt.scene.feed.ss.*; 017 018 019/** The server side of the dummy feed, implementing the feed service. A single instance 020 * of DummyFeedSS is constructed by the servlet container (Tomcat), as defined in its 021 * <code><a href='../../../../../../a/web/context/WEB-INF/web.xml'>web.xml</a></code> 022 * configuration file. The service path is <code>/xfDum</code>. Here is an example of a 023 * service query: 024 * 025 * <blockquote><code><a href='http://reluk.ca:8080/v/xfDum?callback=foo&form=json&pretty'>http://reluk.ca:8080/v/xfDum?callback=foo&form=json&pretty</a></code></blockquote> 026 * 027 * <p>The service responds with a series of {@value BITE_COUNT} dummy bites. Query 028 * parameters for this service are:</p> 029 * 030 * <table class='definition' style='margin-left:1em'> 031 * <tr> 032 * <th class='key'>Key</th> 033 * <th>Value</th> 034 * <th>Default</th> 035 * </tr> 036 * <tr><td class='key'>callback</td> 037 * 038 * <td>The name of the callback function for a JSONP response, or leave it 039 * unspecified for a plain JSON response. This is a qualifier for the 'form' 040 * parameter whenever that parameter is set to 'json'.</td> 041 * 042 * <td>Null, specifying a plain JSON response.</td> 043 * 044 * </tr> 045 * <tr><td class='key'>form</td> 046 * 047 * <td>The form of the response. Currently only 'json' is supported, which gives 048 * either JSON or JSONP depending on the value of the 'callback' parameter.</td> 049 * 050 * <td>None, a value is required.</td> 051 * 052 * </tr> 053 * <tr><td class='key'>pretty</td> 054 * 055 * <td>Specify 'pretty' or 'pretty=y' for a more human-readable response. This 056 * adds whitespace and forces a content type of 'text/plain'.</td> 057 * 058 * <td>'n'</td> 059 * 060 * </tr> 061 * </table> 062 */ 063public final @ThreadSafe class DummyFeedSS extends HttpServlet // FIX by using WAP, per a/web/context/WEB-INF/web.xml 064{ 065 066 067 public @Override void init() throws ServletException 068 { 069 try 070 { 071 vsRun = new VoteServer( VOWicket.contextPathToVoteServerName( // OPT for space: if whole vote-server really needed, servlets ought to share it 072 getServletContext().getContextPath() )).new Run( /*isSingleThreaded*/false ); 073 vsRun.init_done(); // nothing to do here anymore 074 } 075 catch( RuntimeException x ) { throw x; } 076 catch( Exception x ) { throw new ServletException( x ); } 077 } 078 079 080 081 // ------------------------------------------------------------------------------------ 082 083 084 /** The number of dummy bites in each response. 085 */ 086 static final int BITE_COUNT = 10; 087 // 100 was causing client timeout on obsidian, as polls take too long to construct. 088 089 090 091 // - H t t p - S e r v l e t ---------------------------------------------------------- 092 093 094 protected void doGet( final HttpServletRequest request, final HttpServletResponse response ) 095 throws IOException 096 { 097 // This method is also called by the default implementation of doHead(), which 098 // discards the response body after learning its length. 099 try 100 { 101 logger.fine( HTTPServletRequestX.buildRequestSummary(request).toString() ); 102 final boolean isPretty = HTTPServletRequestX.getBooleanParameter( "pretty", request ); 103 final String callback; 104 { 105 final String form = HTTPServletRequestX.getParameterRequired( "form", request ); 106 if( !"json".equals( form )) 107 { 108 throw new HTTPRequestException( /*400*/HttpServletResponse.SC_BAD_REQUEST, 109 "unrecognized 'form' value: " + form ); 110 } 111 112 callback = HTTPServletRequestX.getParameterNonEmpty( "callback", request ); 113 } 114 115 // Commit to response. 116 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 117 final String characterEncoding = "UTF-8"; 118 response.setCharacterEncoding( characterEncoding ); 119 final Writer outBuf = new BufferedWriter( new OutputStreamWriter( 120 response.getOutputStream(), characterEncoding )); 121 if( callback != null ) 122 { 123 outBuf.append( callback ); 124 outBuf.append( '(' ); 125 } 126 // note that response.isCommitted() when outBuf flushes 127 128 try 129 { 130 final JsonWriter outJSON = new JsonWriter( outBuf ); 131 final String contentType; 132 if( isPretty ) 133 { 134 contentType = "text/plain"; 135 outJSON.setIndent( " " ); // otherwise it would be packed 136 } 137 else contentType = callback == null? "application/json": "application/javascript"; 138 response.setContentType( contentType ); 139 140 final Random random = new Random(); 141 final BiteJig bite = new BiteJig(); 142 final Biter[] biters = Biter.U.assembleBiters( vsRun ); 143 int iUsernameRandom = -1; 144 outJSON.beginArray(); 145 for( int b = 0; b < BITE_COUNT; ++b ) 146 { 147 // Configure bite properties known to this feed 148 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 149 for( int p = 0; p < 2; ++p ) 150 { 151 for( ;; ) 152 { 153 int i = random.nextInt( Dummy.USERNAME_ARRAY.length ); 154 if( i == iUsernameRandom ) continue; // avoid duplicates 155 156 iUsernameRandom = i; 157 break; 158 } 159 bite.addPerson().setUser( IDPair.fromUsername( 160 Dummy.USERNAME_ARRAY[iUsernameRandom] )); 161 } 162 163 final PollJig poll = bite.setPoll(); 164 poll.setName( Dummy.POLL_NAME_ARRAY[random.nextInt( 165 Dummy.POLL_NAME_ARRAY.length )]); 166 poll.setIssueType( "Issue" ); // fake it, not actually used by client 167 168 // Maybe configure other properties, too 169 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 170 for( Biter biter: biters ) biter.configure( bite ); 171 172 // Write configuration on wire 173 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 174 bite.serialize( outJSON ); // clears bite, ready for re-configuration 175 } 176 177 outJSON.endArray(); 178 if( callback != null ) 179 { 180 outJSON.flush(); 181 outBuf.append( ");" ); 182 } 183 184 outJSON.close(); // throws exception if JSON imperfect, so don't close in finally block where it may mask a previous exception 185 } 186 finally { outBuf.close(); } // if not already closed 187 } 188 catch( Exception x ) 189 { 190 if( !response.isCommitted() ) 191 { 192 response.sendError( /*500*/HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 193 x.toString() ); 194 } 195 logger.log( LoggerX.WARNING, "Error in service of request (" + request.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( DummyFeedSS.class ); 205 206 207 208 private volatile VoteServer.Run vsRun; // final after init 209 210 211}