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&amp;form=json&amp;pretty'>http://reluk.ca:8080/v/xfDum?callback=foo&amp;form=json&amp;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}