package votorola.a.count.gwt; // Copyright 2011-2012, 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.gwt.core.client.JavaScriptObject; import com.google.gwt.http.client.URL; import com.google.gwt.jsonp.client.JsonpRequest; import com.google.gwt.user.client.rpc.AsyncCallback; import java.util.HashMap; import votorola.a.web.gwt.*; import votorola.a.web.wap.*; import votorola.g.web.gwt.*; /** A cache of counts on the client side. */ public final class CountCache { /** The single instance of CountCache. */ public static CountCache i() { return instance; } private static CountCache instance; { // constructed by CountMod if( instance != null ) throw new IllegalStateException(); instance = CountCache.this; } // ------------------------------------------------------------------------------------ /** Appends the common portion of a {@linkplain votorola.s.wap.CountWAP CountWAP} * request to the string builder. */ static void appendRequestLoc( final String pollName, final StringBuilder b ) { b.append( App.getServletContextLocation() ); b.append( "/wap?wCall=cCount&cPoll=" ).append( URL.encodeQueryString( pollName )); b.append( "&wNonce=" ).append( URLX.serialNonce() ); } /** Requests a count from the remote count engine and stores a record of the request * in this cache. * * @see #requestCount_loc(String,StringBuilder) */ public JsonpRequest requestCount( final String pollName, final String loc, final AsyncCallback callback ) { assert requestRecord(pollName) == null: "count requests not repeated"; return App.i().jsonpWAP().requestObject( loc, new WAPCleanCallback( pollName ) { public void onFailure( final Throwable x ) { final RequestRecord r = requestMap.put( pollName, new RequestRecord() ); if( r != null ) // collision with another request { requestMap.put( pollName, r ); // put it back instead of clobbering it final CountJS count = r.count(); if( count != null ) { callback.onSuccess( count ); return; } } callback.onFailure( x ); } public void onSuccess( final WAPResponseP responseP ) { CountJS count = responseP.cast(); final RequestRecord r = requestMap.put( pollName, new RequestRecord( count )); if( r != null ) // collision with another request { final CountJS countOld = r.count(); if( countOld != null ) { requestMap.put( pollName, r ); // put it back instead of clobbering it callback.onSuccess( countOld ); return; } // else no harm in clobbering it } count.init( pollName ); callback.onSuccess( count ); } }); } /** Appends the location for calls to requestCount and returns the string * builder. */ public static StringBuilder requestCount_loc( final String pollName, final StringBuilder b ) { appendRequestLoc( pollName, b ); b.append( "&cBase" ); return b; } /** Returns a cached record of a count request, or null if no record is cached. */ public RequestRecord requestRecord( final String pollName ) { return requestMap.get( pollName ); } // ==================================================================================== /** A cached record of a count request. */ public static final class RequestRecord { RequestRecord() { count = null; } RequestRecord( final CountJS _count ) { count = _count; } /** The count, or null if the request failed. At present no information * concerning the cause of a failure is recorded. */ public CountJS count() { return count; } private final CountJS count; } // ==================================================================================== /** A response from the {@linkplain votorola.s.wap.CountWAP CountWAP} API. */ public static final class WAPResponse extends JavaScriptObject { protected WAPResponse() {} // "precisely one constructor... protected, empty, and no-argument" WAPResponseP forPoll( final String pollName ) throws WAPException { final WAPResponseP responseP = _forPoll( pollName ); if( responseP == null ) { throw new WAPException( "malformed response, missing count data for poll \"" + pollName + "\"" ); } return responseP; } private native WAPResponseP _forPoll( final String pollName ) /*-{ return this.c.poll[pollName]; }-*/; } // ==================================================================================== /** A particular poll in a response from the count engine's Web API. */ static final class WAPResponseP extends JavaScriptObject { protected WAPResponseP() {} // "precisely one constructor... protected, empty, and no-argument" void ensureClean() throws WAPException { final String errorSummary = summarizeError(); if( errorSummary != null ) throw new WAPException( errorSummary ); } /** Either constructs a summary of the error report, or returns null if no error * was reported. */ native String summarizeError() /*-{ if( !this.error ) return null; var s = ""; for( var name in this.error ) { if( s ) s += ", "; s += name; } return s; }-*/; } //// P r i v a t e /////////////////////////////////////////////////////////////////////// private static final HashMap requestMap = new HashMap(); }