KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > google > gwt > dev > shell > CheckForUpdates


1 /*
2  * Copyright 2006 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */

16 package com.google.gwt.dev.shell;
17
18 import com.google.gwt.dev.About;
19
20 import org.w3c.dom.Document JavaDoc;
21 import org.w3c.dom.Element JavaDoc;
22 import org.w3c.dom.Node JavaDoc;
23 import org.w3c.dom.NodeList JavaDoc;
24 import org.xml.sax.ErrorHandler JavaDoc;
25 import org.xml.sax.SAXException JavaDoc;
26 import org.xml.sax.SAXParseException JavaDoc;
27
28 import java.io.ByteArrayInputStream JavaDoc;
29 import java.io.ByteArrayOutputStream JavaDoc;
30 import java.io.IOException JavaDoc;
31 import java.io.InputStream JavaDoc;
32 import java.net.MalformedURLException JavaDoc;
33 import java.net.URL JavaDoc;
34 import java.net.URLConnection JavaDoc;
35 import java.util.Date JavaDoc;
36 import java.util.prefs.Preferences JavaDoc;
37
38 import javax.xml.parsers.DocumentBuilder JavaDoc;
39 import javax.xml.parsers.DocumentBuilderFactory JavaDoc;
40 import javax.xml.parsers.ParserConfigurationException JavaDoc;
41
42 /**
43  * Orchestrates a best-effort attempt to find out if a new version of GWT is
44  * available.
45  */

46 public abstract class CheckForUpdates {
47
48   /**
49    * Abstract the action to take when an update is available.
50    */

51   public static interface UpdateAvailableCallback {
52     void onUpdateAvailable(String JavaDoc html);
53   }
54
55   protected static final String JavaDoc LAST_SERVER_VERSION = "lastServerVersion";
56   private static final boolean DEBUG_VERSION_CHECK;
57   private static final String JavaDoc FIRST_LAUNCH = "firstLaunch";
58   private static final String JavaDoc NEXT_PING = "nextPing";
59
60   // Uncomment one of constants below to try different variations of failure to
61
// make sure we never interfere with the app running.
62

63   // Check against a fake server to see failure to contact server.
64
// protected static final String QUERY_URL =
65
// "http://nonexistenthost:1111/gwt/currentversion.xml";
66

67   // Check 404 on a real location that doesn't have the file.
68
// protected static final String QUERY_URL =
69
// "http://www.google.com/gwt/currentversion.xml";
70

71   // A test URL for seeing it actually work in a sandbox.
72
// protected static final String QUERY_URL =
73
// "http://localhost/gwt/currentversion.xml";
74

75   // The real URL that should be used.
76
private static final String JavaDoc QUERY_URL = "http://tools.google.com/webtoolkit/currentversion.xml";
77
78   private static final int VERSION_PARTS = 3;
79   private static final String JavaDoc VERSION_REGEXP = "\\d+\\.\\d+\\.\\d+";
80
81   static {
82     // Do this in a static initializer so we can ignore all exceptions.
83
//
84
boolean debugVersionCheck = false;
85     try {
86       if (System.getProperty("gwt.debugVersionCheck") != null) {
87         debugVersionCheck = true;
88       }
89     } catch (Throwable JavaDoc e) {
90       // Always silently ignore any errors.
91
//
92
} finally {
93       DEBUG_VERSION_CHECK = debugVersionCheck;
94     }
95   }
96
97   /**
98    * Determines whether the server version is definitively newer than the client
99    * version. If any errors occur in the comparison, this method returns false
100    * to avoid unwanted erroneous notifications.
101    *
102    * @param clientVersion The current client version
103    * @param serverVersion The current server version
104    * @return true if the server is definitely newer, otherwise false
105    */

106   protected static boolean isServerVersionNewer(String JavaDoc clientVersion,
107       String JavaDoc serverVersion) {
108     if (clientVersion == null || serverVersion == null) {
109       return false;
110     }
111
112     // must match expected format
113
if (!clientVersion.matches(VERSION_REGEXP)
114         || !serverVersion.matches(VERSION_REGEXP)) {
115       return false;
116     }
117
118     // extract the relevant parts
119
String JavaDoc[] clientParts = clientVersion.split("\\.");
120     String JavaDoc[] serverParts = serverVersion.split("\\.");
121     if (clientParts.length != VERSION_PARTS
122         || serverParts.length != VERSION_PARTS) {
123       return false;
124     }
125
126     // examine piece by piece from most significant to least significant
127
for (int i = 0; i < VERSION_PARTS; ++i) {
128       try {
129         int clientPart = Integer.parseInt(clientParts[i]);
130         int serverPart = Integer.parseInt(serverParts[i]);
131         if (serverPart < clientPart) {
132           return false;
133         }
134
135         if (serverPart > clientPart) {
136           return true;
137         }
138       } catch (NumberFormatException JavaDoc e) {
139         return false;
140       }
141     }
142
143     return false;
144   }
145
146   private static String JavaDoc getTextOfLastElementHavingTag(Document JavaDoc doc,
147       String JavaDoc tagName) {
148     NodeList JavaDoc nodeList = doc.getElementsByTagName(tagName);
149     int n = nodeList.getLength();
150     if (n > 0) {
151       Element JavaDoc elem = (Element JavaDoc) nodeList.item(n - 1);
152       // Assume the first child is the value.
153
//
154
Node JavaDoc firstChild = elem.getFirstChild();
155       if (firstChild != null) {
156         String JavaDoc text = firstChild.getNodeValue();
157         return text;
158       }
159     }
160
161     return null;
162   }
163
164   private static void parseResponse(Preferences JavaDoc prefs, byte[] response,
165       UpdateAvailableCallback callback) throws IOException JavaDoc,
166       ParserConfigurationException JavaDoc, SAXException JavaDoc {
167
168     if (DEBUG_VERSION_CHECK) {
169       System.out.println("Parsing response (length " + response.length + ")");
170     }
171
172     DocumentBuilderFactory JavaDoc factory = DocumentBuilderFactory.newInstance();
173     DocumentBuilder JavaDoc builder = factory.newDocumentBuilder();
174     ByteArrayInputStream JavaDoc bais = new ByteArrayInputStream JavaDoc(response);
175
176     // Parse the XML.
177
//
178
builder.setErrorHandler(new ErrorHandler JavaDoc() {
179
180       public void error(SAXParseException JavaDoc exception) throws SAXException JavaDoc {
181         // fail quietly
182
}
183
184       public void fatalError(SAXParseException JavaDoc exception) throws SAXException JavaDoc {
185         // fail quietly
186
}
187
188       public void warning(SAXParseException JavaDoc exception) throws SAXException JavaDoc {
189         // fail quietly
190
}
191     });
192     Document JavaDoc doc = builder.parse(bais);
193
194     // The latest version number.
195
//
196
String JavaDoc version = getTextOfLastElementHavingTag(doc, "latest-version");
197     if (version == null) {
198       // Not valid; quietly fail.
199
//
200
if (DEBUG_VERSION_CHECK) {
201         System.out.println("Failed to find <latest-version>");
202       }
203       return;
204     } else {
205       version = version.trim();
206     }
207
208     String JavaDoc[] versionParts = version.split("\\.");
209     if (versionParts.length != 3) {
210       // Not valid; quietly fail.
211
//
212
if (DEBUG_VERSION_CHECK) {
213         System.out.println("Bad version format: " + version);
214       }
215       return;
216     }
217     try {
218       Integer.parseInt(versionParts[0]);
219       Integer.parseInt(versionParts[1]);
220       Integer.parseInt(versionParts[2]);
221     } catch (NumberFormatException JavaDoc e) {
222       // Not valid; quietly fail.
223
//
224
if (DEBUG_VERSION_CHECK) {
225         System.out.println("Bad version number: " + version);
226       }
227       return;
228     }
229
230     // Ping delay for server-controlled throttling.
231
//
232
String JavaDoc pingDelaySecsStr = getTextOfLastElementHavingTag(doc,
233         "min-wait-seconds");
234     int pingDelaySecs = 0;
235     if (pingDelaySecsStr == null) {
236       // Not valid; quietly fail.
237
//
238
if (DEBUG_VERSION_CHECK) {
239         System.out.println("Missing <min-wait-seconds>");
240       }
241       return;
242     } else {
243       try {
244         pingDelaySecs = Integer.parseInt(pingDelaySecsStr.trim());
245       } catch (NumberFormatException JavaDoc e) {
246         // Not a valid number; quietly fail.
247
//
248
if (DEBUG_VERSION_CHECK) {
249           System.out.println("Bad min-wait-seconds number: " + pingDelaySecsStr);
250         }
251         return;
252       }
253     }
254
255     // Read the HTML.
256
//
257
String JavaDoc html = getTextOfLastElementHavingTag(doc, "notification");
258
259     if (html == null) {
260       // Not valid; quietly fail.
261
//
262
if (DEBUG_VERSION_CHECK) {
263         System.out.println("Missing <notification>");
264       }
265       return;
266     }
267
268     // Okay -- this is a valid response.
269
//
270
processResponse(prefs, version, pingDelaySecs, html, callback);
271   }
272
273   private static void processResponse(Preferences JavaDoc prefs, String JavaDoc version,
274       int pingDelaySecs, String JavaDoc html, UpdateAvailableCallback callback) {
275
276     // Record a ping; don't ping again until the delay is up.
277
//
278
long nextPingTime = System.currentTimeMillis() + pingDelaySecs * 1000;
279     prefs.put(NEXT_PING, String.valueOf(nextPingTime));
280
281     if (DEBUG_VERSION_CHECK) {
282       System.out.println("Ping delay is " + pingDelaySecs + "; next ping at "
283           + new Date JavaDoc(nextPingTime));
284     }
285
286     /*
287      * Stash the version we got last time for comparison below, and record for
288      * next time the version we just got.
289      */

290     String JavaDoc lastServerVersion = prefs.get(LAST_SERVER_VERSION, null);
291     prefs.put(LAST_SERVER_VERSION, version);
292
293     // Are we up to date already?
294
//
295
if (!isServerVersionNewer(About.GWT_VERSION_NUM, version)) {
296
297       // Yes, we are.
298
//
299
if (DEBUG_VERSION_CHECK) {
300         System.out.println("Server version is not newer");
301       }
302       return;
303     }
304
305     // Have we already prompted for this particular server version?
306
//
307
if (version.equals(lastServerVersion)) {
308
309       // We've already nagged the user once. Don't do it again.
310
//
311
if (DEBUG_VERSION_CHECK) {
312         System.out.println("A notification has already been shown for "
313             + version);
314       }
315       return;
316     }
317
318     if (DEBUG_VERSION_CHECK) {
319       System.out.println("Server version has changed to " + version
320           + "; notification will be shown");
321     }
322
323     // Commence nagging.
324
//
325
callback.onUpdateAvailable(html);
326   }
327
328   public void check(final UpdateAvailableCallback callback) {
329
330     try {
331       String JavaDoc forceCheckURL = System.getProperty("gwt.forceVersionCheckURL");
332
333       if (forceCheckURL != null && DEBUG_VERSION_CHECK) {
334         System.out.println("Explicit version check URL: " + forceCheckURL);
335       }
336
337       // Get our unique user id (based on absolute timestamp).
338
//
339
long currentTimeMillis = System.currentTimeMillis();
340       Preferences JavaDoc prefs = Preferences.userNodeForPackage(CheckForUpdates.class);
341
342       // Get our unique user id (based on absolute timestamp).
343
//
344
String JavaDoc firstLaunch = prefs.get(FIRST_LAUNCH, null);
345       if (firstLaunch == null) {
346         firstLaunch = Long.toHexString(currentTimeMillis);
347         prefs.put(FIRST_LAUNCH, firstLaunch);
348
349         if (DEBUG_VERSION_CHECK) {
350           System.out.println("Setting first launch to " + firstLaunch);
351         }
352       } else {
353         if (DEBUG_VERSION_CHECK) {
354           System.out.println("First launch was " + firstLaunch);
355         }
356       }
357
358       // See if it's time for our next ping yet.
359
//
360
String JavaDoc nextPing = prefs.get(NEXT_PING, "0");
361       if (nextPing != null) {
362         try {
363           long nextPingTime = Long.parseLong(nextPing);
364           if (currentTimeMillis < nextPingTime) {
365             // it's not time yet
366
if (DEBUG_VERSION_CHECK) {
367               System.out.println("Next ping is not until "
368                   + new Date JavaDoc(nextPingTime));
369             }
370             return;
371           }
372         } catch (NumberFormatException JavaDoc e) {
373           // ignore
374
}
375       }
376
377       // See if new version is available.
378
//
379
String JavaDoc queryURL = forceCheckURL != null ? forceCheckURL : QUERY_URL;
380       String JavaDoc url = queryURL + "?v=" + About.GWT_VERSION_NUM + "&id="
381           + firstLaunch;
382
383       if (DEBUG_VERSION_CHECK) {
384         System.out.println("Checking for new version at " + url);
385       }
386
387       // Do the HTTP GET.
388
//
389
byte[] response;
390       String JavaDoc fullUserAgent = makeUserAgent();
391       if (System.getProperty("gwt.forceVersionCheckNonNative") == null) {
392         // Use subclass.
393
//
394
response = doHttpGet(fullUserAgent, url);
395       } else {
396         // Use the pure Java version, but it probably doesn't work with proxies.
397
//
398
response = httpGetNonNative(fullUserAgent, url);
399       }
400
401       if (response == null) {
402         // Problem. Quietly fail.
403
//
404
if (DEBUG_VERSION_CHECK) {
405           System.out.println("Failed to obtain current version info via HTTP");
406         }
407         return;
408       }
409
410       // Parse and process the response.
411
// Bad responses will be silently ignored.
412
//
413
parseResponse(prefs, response, callback);
414
415     } catch (Throwable JavaDoc e) {
416       // Always silently ignore any errors.
417
//
418
if (DEBUG_VERSION_CHECK) {
419         System.out.println("Exception while processing version info");
420         e.printStackTrace();
421       }
422     }
423   }
424
425   protected abstract byte[] doHttpGet(String JavaDoc userAgent, String JavaDoc url);
426
427   /**
428    * This default implementation uses regular Java HTTP, which doesn't deal with
429    * proxies automagically. See the IE6 subclasses for an implementation that
430    * does deal with proxies.
431    */

432   protected byte[] httpGetNonNative(String JavaDoc userAgent, String JavaDoc url) {
433     Throwable JavaDoc caught;
434     InputStream JavaDoc is = null;
435     try {
436       URL JavaDoc urlToGet = new URL JavaDoc(url);
437       URLConnection JavaDoc conn = urlToGet.openConnection();
438       conn.setRequestProperty("User-Agent", userAgent);
439       is = conn.getInputStream();
440       ByteArrayOutputStream JavaDoc baos = new ByteArrayOutputStream JavaDoc();
441       byte[] buffer = new byte[4096];
442       int bytesRead;
443       while ((bytesRead = is.read(buffer)) != -1) {
444         baos.write(buffer, 0, bytesRead);
445       }
446       byte[] response = baos.toByteArray();
447       return response;
448     } catch (MalformedURLException JavaDoc e) {
449       caught = e;
450     } catch (IOException JavaDoc e) {
451       caught = e;
452     } finally {
453       if (is != null) {
454         try {
455           is.close();
456         } catch (IOException JavaDoc e) {
457         }
458       }
459     }
460
461     if (System.getProperty("gwt.debugLowLevelHttpGet") != null) {
462       caught.printStackTrace();
463     }
464
465     return null;
466   }
467
468   private void appendUserAgentProperty(StringBuffer JavaDoc sb, String JavaDoc propName) {
469     String JavaDoc propValue = System.getProperty(propName);
470     if (propValue != null) {
471       if (sb.length() > 0) {
472         sb.append("; ");
473       }
474       sb.append(propName);
475       sb.append("=");
476       sb.append(propValue);
477     }
478   }
479
480   /**
481    * Creates a user-agent string by combining standard Java properties.
482    */

483   private String JavaDoc makeUserAgent() {
484     String JavaDoc ua = "GWT Freshness Checker";
485
486     StringBuffer JavaDoc extra = new StringBuffer JavaDoc();
487     appendUserAgentProperty(extra, "java.vendor");
488     appendUserAgentProperty(extra, "java.version");
489     appendUserAgentProperty(extra, "os.arch");
490     appendUserAgentProperty(extra, "os.name");
491     appendUserAgentProperty(extra, "os.version");
492
493     if (extra.length() > 0) {
494       ua += " (" + extra.toString() + ")";
495     }
496
497     return ua;
498   }
499 }
500
Popular Tags