001package 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.
002
003import com.google.gwt.core.client.JavaScriptObject;
004import com.google.gwt.http.client.URL;
005import com.google.gwt.jsonp.client.JsonpRequest;
006import com.google.gwt.user.client.rpc.AsyncCallback;
007import java.util.HashMap;
008import votorola.a.web.gwt.*;
009import votorola.a.web.wap.*;
010import votorola.g.web.gwt.*;
011
012
013/** A cache of counts on the client side.
014  */
015public final class CountCache
016{
017
018
019    /** The single instance of CountCache.
020      */
021    public static CountCache i() { return instance; }
022
023
024        private static CountCache instance;
025
026        {   // constructed by CountMod
027            if( instance != null ) throw new IllegalStateException();
028
029            instance = CountCache.this;
030        }
031
032
033
034   // ------------------------------------------------------------------------------------
035
036
037    /** Appends the common portion of a {@linkplain votorola.s.wap.CountWAP CountWAP}
038      * request to the string builder.
039      */
040    static void appendRequestLoc( final String pollName, final StringBuilder b )
041    {
042        b.append( App.getServletContextLocation() );
043        b.append( "/wap?wCall=cCount&cPoll=" ).append( URL.encodeQueryString( pollName ));
044        b.append( "&wNonce=" ).append( URLX.serialNonce() );
045    }
046
047
048
049    /** Requests a count from the remote count engine and stores a record of the request
050      * in this cache.
051      *
052      *     @see #requestCount_loc(String,StringBuilder)
053      */
054    public JsonpRequest<WAPResponse> requestCount( final String pollName, final String loc,
055      final AsyncCallback<CountJS> callback )
056    {
057        assert requestRecord(pollName) == null: "count requests not repeated";
058        return App.i().jsonpWAP().requestObject( loc, new WAPCleanCallback( pollName )
059        {
060            public void onFailure( final Throwable x )
061            {
062                final RequestRecord r = requestMap.put( pollName, new RequestRecord() );
063                if( r != null  ) // collision with another request
064                {
065                    requestMap.put( pollName, r ); // put it back instead of clobbering it
066                    final CountJS count = r.count();
067                    if( count != null )
068                    {
069                        callback.onSuccess( count );
070                        return;
071                    }
072                }
073
074                callback.onFailure( x );
075            }
076
077            public void onSuccess( final WAPResponseP responseP )
078            {
079                CountJS count = responseP.cast();
080                final RequestRecord r = requestMap.put( pollName, new RequestRecord( count ));
081                if( r != null  ) // collision with another request
082                {
083                    final CountJS countOld = r.count();
084                    if( countOld != null )
085                    {
086                        requestMap.put( pollName, r ); // put it back instead of clobbering it
087                        callback.onSuccess( countOld );
088                        return;
089                    }
090                    // else no harm in clobbering it
091                }
092                count.init( pollName );
093                callback.onSuccess( count );
094            }
095        });
096    }
097
098
099
100    /** Appends the location for calls to <code>requestCount</code> and returns the string
101      * builder.
102      */
103    public static StringBuilder requestCount_loc( final String pollName, final StringBuilder b )
104    {
105        appendRequestLoc( pollName, b );
106        b.append( "&cBase" );
107        return b;
108    }
109
110
111
112    /** Returns a cached record of a count request, or null if no record is cached.
113      */
114    public RequestRecord requestRecord( final String pollName )
115    {
116        return requestMap.get( pollName );
117    }
118
119
120
121   // ====================================================================================
122
123
124    /** A cached record of a count request.
125      */
126    public static final class RequestRecord
127    {
128
129        RequestRecord() { count = null; }
130
131
132        RequestRecord( final CountJS _count ) { count = _count; }
133
134
135        /** The count, or null if the request failed.  At present no information
136          * concerning the cause of a failure is recorded.
137          */
138        public CountJS count() { return count; }
139
140
141            private final CountJS count;
142
143
144    }
145
146
147
148   // ====================================================================================
149
150
151    /** A response from the {@linkplain votorola.s.wap.CountWAP CountWAP} API.
152      */
153    public static final class WAPResponse extends JavaScriptObject
154    {
155
156        protected WAPResponse() {} // "precisely one constructor... protected, empty, and no-argument"
157
158
159        WAPResponseP forPoll( final String pollName ) throws WAPException
160        {
161            final WAPResponseP responseP = _forPoll( pollName );
162            if( responseP == null )
163            {
164                throw new WAPException( "malformed response, missing count data for poll \""
165                  + pollName + "\"" );
166            }
167
168            return responseP;
169        }
170
171
172        private native WAPResponseP _forPoll( final String pollName )
173        /*-{
174            return this.c.poll[pollName];
175        }-*/;
176
177    }
178
179
180
181   // ====================================================================================
182
183
184    /** A particular poll in a response from the count engine's Web API.
185      */
186    static final class WAPResponseP extends JavaScriptObject
187    {
188
189        protected WAPResponseP() {} // "precisely one constructor... protected, empty, and no-argument"
190
191
192        void ensureClean() throws WAPException
193        {
194            final String errorSummary = summarizeError();
195            if( errorSummary != null ) throw new WAPException( errorSummary );
196        }
197
198
199        /** Either constructs a summary of the error report, or returns null if no error
200          * was reported.
201          */
202        native String summarizeError()
203        /*-{
204            if( !this.error ) return null;
205
206            var s = "";
207            for( var name in this.error )
208            {
209                if( s ) s += ", ";
210                s += name;
211            }
212            return s;
213        }-*/;
214
215
216    }
217
218
219
220//// P r i v a t e ///////////////////////////////////////////////////////////////////////
221
222
223    private static final HashMap<String,RequestRecord> requestMap =
224      new HashMap<String,RequestRecord>();
225
226
227
228}