KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > nl > justobjects > pushlet > client > PushletClient


1 // Copyright (c) 2000 Just Objects B.V. <just@justobjects.nl>
2
// Distributable under LGPL license. See terms of license at gnu.org.
3

4 package nl.justobjects.pushlet.client;
5
6 import nl.justobjects.pushlet.core.Event;
7 import nl.justobjects.pushlet.core.EventParser;
8 import nl.justobjects.pushlet.core.Protocol;
9 import nl.justobjects.pushlet.util.PushletException;
10
11 import java.io.IOException JavaDoc;
12 import java.io.InputStreamReader JavaDoc;
13 import java.io.Reader JavaDoc;
14 import java.io.OutputStream JavaDoc;
15 import java.net.URL JavaDoc;
16 import java.net.URLConnection JavaDoc;
17 import java.net.Authenticator JavaDoc;
18 import java.net.PasswordAuthentication JavaDoc;
19 import java.util.Map JavaDoc;
20
21 /**
22  * Client API for Java HTTP client applets or apps.
23  * <p>
24  * Use this class within Java client applications or applets.
25  * Implement a PushletClientListener to receive callbacks for
26  * data-related Event objects pushed by the server.
27  *
28  * @version $Id: PushletClient.java,v 1.13 2005/02/28 16:59:40 justb Exp $
29  * @author Just van den Broecke - Just Objects &copy;
30  *
31  * @see PushletClientListener
32  * @see nl.justobjects.pushlet.test.PushletApplet
33  * @see nl.justobjects.pushlet.test.PushletPingApplication
34  */

35 public class PushletClient implements Protocol {
36     /** Pushlet URL. */
37     private String JavaDoc pushletURL;
38
39     /** Debug flag for verbose output. */
40     private boolean debug;
41
42     /** Id gotten on join ack */
43     private String JavaDoc id;
44
45     /** Internal listener for data events pushed by server. */
46     private DataEventListener dataEventListener;
47
48     /** Default constructor. */
49     public PushletClient(String JavaDoc aPushletURL) {
50         pushletURL = aPushletURL;
51     }
52
53     /** Constructor with host and port using default URI. */
54     public PushletClient(String JavaDoc aHost, int aPort) {
55         this("http://" + aHost + ":" + aPort + DEFAULT_SERVLET_URI);
56     }
57
58     /**
59      * Set proxy options and optional proxy authentication.
60      *
61      * Contributed by Dele Olajide
62      * See http://groups.yahoo.com/group/pushlet/message/634
63      *
64      * Usage:
65      * PushletClient pushletClient = new PushletClient("http:://www.domain.com/pushlet");
66      * pushletClient.setProxyOptions("proxy.bla.com", "8080", ....);
67      *
68      * use pushletClient further as normal
69      *
70      */

71     public void setProxyOptions(String JavaDoc aProxyHost,
72             String JavaDoc aProxyPort, String JavaDoc theNonProxyHosts,
73             String JavaDoc aUserName, String JavaDoc aPassword, String JavaDoc anNTLMDomain) {
74
75         // Enable proxying
76
System.setProperty("http.proxySet", "true");
77         System.setProperty("http.proxyHost", aProxyHost);
78         System.setProperty("http.proxyPort", aProxyPort);
79
80         // Set optional non-proxy hosts
81
if (theNonProxyHosts != null) {
82             System.setProperty("http.nonProxyHosts", theNonProxyHosts);
83         }
84
85         // If user name specified configure proxy authentication
86
if (aUserName != null) {
87             System.setProperty("http.proxyUser", aUserName);
88             System.setProperty("http.proxyPassword", aPassword);
89
90             // See inner class below
91
Authenticator.setDefault(new HTTPAuthenticateProxy(aUserName, aPassword));
92
93             // Optional NT domain
94
if (anNTLMDomain != null) {
95                 System.setProperty("http.auth.ntlm.domain", anNTLMDomain);
96             }
97         }
98     }
99
100     /** Join server, starts session. */
101     public void join() throws PushletException {
102         Event event = new Event(E_JOIN);
103         event.setField(P_FORMAT, FORMAT_XML);
104         Event response = doControl(event);
105         throwOnNack(response);
106
107         // Join Ack received
108
id = response.getField(P_ID);
109     }
110
111     /** Leave server, stops session. */
112     public void leave() throws PushletException {
113         stopListen();
114         throwOnInvalidSession();
115         Event event = new Event(E_LEAVE);
116         event.setField(P_ID, id);
117         Event response = doControl(event);
118
119         throwOnNack(response);
120         id = null;
121     }
122
123     /** Open data channel. */
124     public void listen(PushletClientListener aListener) throws PushletException {
125         listen(aListener, MODE_STREAM);
126     }
127
128     /** Open data channel in stream or push mode. */
129     public void listen(PushletClientListener aListener, String JavaDoc aMode) throws PushletException {
130         listen(aListener, aMode, null);
131     }
132
133     /** Open data channel in stream or push mode with a subject. */
134     public void listen(PushletClientListener aListener, String JavaDoc aMode, String JavaDoc aSubject) throws PushletException {
135         throwOnInvalidSession();
136         stopListen();
137
138         String JavaDoc listenURL = pushletURL
139                 + "?" + P_EVENT + "=" + E_LISTEN
140                 + "&" + P_ID + "=" + id
141                 + "&" + P_MODE + "=" + aMode
142                 ;
143         if (aSubject != null) {
144             listenURL = listenURL + "&" + P_SUBJECT + "=" + aSubject;
145         }
146
147         dataEventListener = new DataEventListener(aListener, listenURL);
148         dataEventListener.start();
149     }
150
151     /** Immediate listener. */
152     public void joinListen(PushletClientListener aListener, String JavaDoc aMode, String JavaDoc aSubject) throws PushletException {
153         stopListen();
154
155         String JavaDoc listenURL = pushletURL
156                 + "?" + P_EVENT + "=" + E_JOIN_LISTEN
157                 + "&" + P_FORMAT + "=" + FORMAT_XML
158                 + "&" + P_MODE + "=" + aMode
159                 + "&" + P_SUBJECT + "=" + aSubject
160                 ;
161
162         dataEventListener = new DataEventListener(aListener, listenURL);
163         dataEventListener.start();
164     }
165     /** Publish an event through server. */
166     public void publish(String JavaDoc aSubject, Map JavaDoc theAttributes) throws PushletException {
167         throwOnInvalidSession();
168         Event event = new Event(E_PUBLISH, theAttributes);
169         event.setField(P_SUBJECT, aSubject);
170         event.setField(P_ID, id);
171         Event response = doControl(event);
172         throwOnNack(response);
173     }
174
175     /** Subscribes, returning subscription id. */
176     public String JavaDoc subscribe(String JavaDoc aSubject, String JavaDoc aLabel) throws PushletException {
177         throwOnInvalidSession();
178         Event event = new Event(E_SUBSCRIBE);
179         event.setField(P_ID, id);
180         event.setField(P_SUBJECT, aSubject);
181
182         // Optional label, is returned in data events
183
if (aLabel != null) {
184             event.setField(P_SUBSCRIPTION_LABEL, aLabel);
185         }
186
187         // Send request
188
Event response = doControl(event);
189         throwOnNack(response);
190
191         return response.getField(P_SUBSCRIPTION_ID);
192     }
193
194     /** Subscribes, returning subscription id. */
195     public String JavaDoc subscribe(String JavaDoc aSubject) throws PushletException {
196         return subscribe(aSubject, null);
197     }
198
199     /** Unsubscribes with subscription id. */
200     public void unsubscribe(String JavaDoc aSubscriptionId) throws PushletException {
201         throwOnInvalidSession();
202         Event event = new Event(E_UNSUBSCRIBE);
203         event.setField(P_ID, id);
204
205         // Optional subscription id
206
if (aSubscriptionId != null) {
207             event.setField(P_SUBSCRIPTION_ID, aSubscriptionId);
208         }
209
210         Event response = doControl(event);
211         throwOnNack(response);
212     }
213
214     /** Unsubscribes from all subjects. */
215     public void unsubscribe() throws PushletException {
216         unsubscribe(null);
217     }
218
219     /** Stop the listener. */
220     public void stopListen() throws PushletException {
221         if (dataEventListener != null) {
222             unsubscribe();
223             dataEventListener.stop();
224             dataEventListener = null;
225         }
226     }
227
228     public void setDebug(boolean b) {
229         debug = b;
230     }
231
232     private void throwOnNack(Event anEvent) throws PushletException {
233         if (anEvent.getEventType().equals(E_NACK)) {
234             throw new PushletException("Negative response: reason=" + anEvent.getField(P_REASON));
235         }
236     }
237
238     private void throwOnInvalidSession() throws PushletException {
239         if (id == null) {
240             throw new PushletException("Invalid pushlet session");
241         }
242     }
243
244     private Reader JavaDoc openURL(String JavaDoc aURL) throws PushletException {
245         // Open URL connection with server
246
try {
247             p("Connecting to " + aURL);
248             URL JavaDoc url = new URL JavaDoc(aURL);
249             URLConnection JavaDoc urlConnection = url.openConnection();
250
251             // Disable any kind of caching.
252
urlConnection.setUseCaches(false);
253             urlConnection.setDefaultUseCaches(false);
254
255             // TODO: later version may use POST
256
// Enable HTTP POST
257
// urlConnection.setDoOutput(true);
258

259             // Do the POST with Event in XML in body
260
// OutputStream os = urlConnection.getOutputStream();
261
// os.write(anEvent.toXML().getBytes());
262
// os.flush();
263
// os.close();
264

265             // Get the stream from the server.
266
// reader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
267
// Note: somehow the client does not work with some JVMs when using
268
// BufferedInputStream... So do unbuffered input.
269
// p("Opening urlConnection inputstream");
270
return new InputStreamReader JavaDoc(urlConnection.getInputStream());
271
272         } catch (Throwable JavaDoc t) {
273             warn("openURL() could not open " + aURL, t);
274             throw new PushletException(" could not open " + aURL, t);
275         }
276     }
277
278
279     private Event doControl(Event aControlEvent) throws PushletException {
280         String JavaDoc controlURL = pushletURL + "?" + aControlEvent.toQueryString();
281
282         p("doControl to " + controlURL);
283
284         // Open URL connection with server
285
Reader JavaDoc reader = openURL(controlURL);
286
287         // Get Pushlet event from stream
288
Event event = null;
289         try {
290             p("Getting event...");
291             // Get next event from server
292
event = EventParser.parse(reader);
293             p("Event received " + event);
294             return event;
295         } catch (Throwable JavaDoc t) {
296             // Stop and report error.
297
warn("doControl() exception", t);
298             throw new PushletException(" error parsing response from" + controlURL, t);
299         }
300     }
301
302     /** Util: print. */
303     private void p(String JavaDoc s) {
304         if (debug) {
305             System.out.println("[PushletClient] " + s);
306         }
307     }
308
309     /** Util: warn. */
310     private void warn(String JavaDoc s) {
311         warn(s, null);
312     }
313
314     /** Util: warn with exception. */
315     private void warn(String JavaDoc s, Throwable JavaDoc t) {
316         System.err.println("[PushletClient] - WARN - " + s + " ex=" + t);
317
318         if (t != null) {
319             t.printStackTrace();
320         }
321     }
322
323     /** Internal listener for the Pushlet data channel. */
324     private class DataEventListener implements Runnable JavaDoc {
325         /** Client's listener that gets called back on events. */
326         private PushletClientListener listener;
327
328         /** Receiver receiveThread. */
329         private Thread JavaDoc receiveThread = null;
330         private Reader JavaDoc reader;
331         private String JavaDoc refreshURL;
332         private String JavaDoc listenURL;
333
334         public DataEventListener(PushletClientListener aListener, String JavaDoc aListenURL) {
335             listener = aListener;
336             listenURL = aListenURL;
337         }
338
339         public void start() {
340             // All ok: start a receiver receiveThread
341
receiveThread = new Thread JavaDoc(this);
342             receiveThread.start();
343
344         }
345
346         /** Stop listening; may restart later with start(). */
347         public void stop() {
348             p("In stop()");
349             bailout();
350         }
351
352         /** Receive event objects from server and callback listener. */
353         public void run() {
354             p("Start run()");
355             try {
356                 while (receiveThread != null && receiveThread.isAlive()) {
357                     // Connect to server
358
reader = openURL(listenURL);
359
360                     // Get events while we're alive.
361
while (receiveThread != null && receiveThread.isAlive()) {
362                         Event event = null;
363                         try {
364                             // p("Getting event...");
365
// Get next event from server
366
event = EventParser.parse(reader);
367                             p("Event received " + event);
368                         } catch (Throwable JavaDoc t) {
369
370                             // Stop and report error.
371
// warn("Stop run() on exception", t);
372
if (listener != null) {
373                                 listener.onError("exception during receive: " + t);
374                             }
375
376                             bailout();
377                             return;
378                         }
379
380                         // Handle event by calling listener
381
if (event != null && listener != null) {
382                             // p("received: " + event.toXML());
383
String JavaDoc eventType = event.getEventType();
384                             if (eventType.equals(E_HEARTBEAT)) {
385                                 listener.onHeartbeat(event);
386                             } else if (eventType.equals(E_DATA)) {
387                                 listener.onData(event);
388                             } else if (eventType.equals(E_JOIN_LISTEN_ACK)) {
389                                 id = event.getField(P_ID);
390                             } else if (eventType.equals(E_LISTEN_ACK)) {
391                                 p("Listen ack ok");
392                             } else if (eventType.equals(E_REFRESH_ACK)) {
393                                 // ignore
394
} else if (eventType.equals(E_ABORT)) {
395                                 listener.onAbort(event);
396                             } else if (eventType.equals(E_REFRESH)) {
397                                 refresh(event);
398                             } else {
399                                 warn("unsupported event type received: " + eventType);
400                             }
401                         }
402                     }
403                 }
404             } catch (Throwable JavaDoc t) {
405                 warn("Exception in run() ", t);
406                 bailout();
407             }
408         }
409
410         private void disconnect() {
411             p("start disconnect()");
412             if (reader != null) {
413                 try {
414                     // this blocks, find another way
415
// reader.close();
416
p("Closed reader ok");
417                 } catch (Exception JavaDoc ignore) {
418                 } finally {
419                     reader = null;
420                 }
421             }
422             p("end disconnect()");
423         }
424
425         /** Stop receiver receiveThread. */
426         public void stopThread() {
427             p("In stopThread()");
428
429             // Keep a reference such that we can kill it from here.
430
Thread JavaDoc targetThread = receiveThread;
431
432             receiveThread = null;
433
434             // This should stop the main loop for this receiveThread.
435
// Killing a receiveThread on a blcing read is tricky.
436
// See also http://gee.cs.oswego.edu/dl/cpj/cancel.html
437
if ((targetThread != null) && targetThread.isAlive()) {
438
439                 targetThread.interrupt();
440
441                 try {
442
443                     // Wait for it to die
444
targetThread.join(500);
445                 } catch (InterruptedException JavaDoc ignore) {
446                 }
447
448                 // If current receiveThread refuses to die,
449
// take more rigorous methods.
450
if (targetThread.isAlive()) {
451
452                     // Not preferred but may be needed
453
// to stop during a blocking read.
454
targetThread.stop();
455
456                     // Wait for it to die
457
try {
458                         targetThread.join(500);
459                     } catch (Throwable JavaDoc ignore) {
460                     }
461                 }
462
463                 p("Stopped receiveThread alive=" + targetThread.isAlive());
464
465             }
466         }
467
468         /** Stop listening on stream from server. */
469         public void bailout() {
470             p("In bailout()");
471             stopThread();
472             disconnect();
473         }
474
475         /** Handle refresh, by pausing. */
476         private void refresh(Event aRefreshEvent) throws PushletException {
477             try {
478                 // Wait for specified time.
479
Thread.sleep(Long.parseLong(aRefreshEvent.getField(P_WAIT)));
480             } catch (Throwable JavaDoc t) {
481                 warn("abort while refresing");
482                 refreshURL = null;
483                 return;
484             }
485
486             // If stopped during sleep, don't proceed
487
if (receiveThread == null) {
488                 return;
489             }
490
491             // Create url to refresh
492
refreshURL = pushletURL
493                     + "?" + P_ID + "=" + id
494                     + "&" + P_EVENT + "=" + E_REFRESH
495                     ;
496
497             if (reader != null) {
498                 try {
499                     reader.close();
500
501                 } catch (IOException JavaDoc ignore) {
502
503                 }
504                 reader = null;
505             }
506
507             reader = openURL(refreshURL);
508         }
509     }
510
511     /** Authenticator */
512     private static class HTTPAuthenticateProxy extends Authenticator JavaDoc {
513
514         /**
515          * Contributed by Dele Olajide
516          * See http://groups.yahoo.com/group/pushlet/message/634
517          */

518
519         private String JavaDoc thePassword = "";
520         private String JavaDoc theUser = "";
521
522         public HTTPAuthenticateProxy(String JavaDoc username, String JavaDoc password) {
523
524             thePassword = password;
525             theUser = username;
526         }
527
528         protected PasswordAuthentication JavaDoc getPasswordAuthentication() {
529             // System.out.println("[HttpAuthenticateProxy] Username = " + theUser);
530
// System.out.println("[HttpAuthenticateProxy] Password = " + thePassword);
531

532             return new PasswordAuthentication JavaDoc(theUser, thePassword.toCharArray());
533         }
534
535     }
536
537 }
538
539
540 /*
541  * $Log: PushletClient.java,v $
542  * Revision 1.13 2005/02/28 16:59:40 justb
543  * fixes for leave and disconnect
544  *
545  * Revision 1.12 2005/02/28 15:57:54 justb
546  * added SimpleListener example
547  *
548  * Revision 1.11 2005/02/21 12:31:44 justb
549  * added proxy contribution from Dele Olajide
550  *
551  * Revision 1.10 2005/02/20 13:05:32 justb
552  * removed the Postlet (integrated in Pushlet protocol)
553  *
554  * Revision 1.9 2005/02/18 10:07:23 justb
555  * many renamings of classes (make names compact)
556  *
557  * Revision 1.8 2005/02/18 09:54:12 justb
558  * refactor: rename Publisher Dispatcher and single Subscriber class
559  *
560  * Revision 1.7 2005/02/15 15:46:30 justb
561  * client API improves
562  *
563  * Revision 1.6 2005/02/15 13:28:56 justb
564  * first quick rewrite adapt for v2 protocol
565  *
566  * Revision 1.5 2004/10/25 21:23:44 justb
567  * *** empty log message ***
568  *
569  * Revision 1.4 2004/10/24 13:52:51 justb
570  * small fixes in client lib
571  *
572  * Revision 1.3 2004/10/24 12:58:18 justb
573  * revised client and test classes for new protocol
574  *
575  * Revision 1.2 2004/09/03 22:35:37 justb
576  * Almost complete rewrite, just checking in now
577  *
578  * Revision 1.1 2004/03/10 20:14:17 justb
579  * renamed all *JavaPushletClient* to *PushletClient*
580  *
581  * Revision 1.10 2004/03/10 15:45:55 justb
582  * many cosmetic changes
583  *
584  * Revision 1.9 2003/08/17 20:30:20 justb
585  * cosmetic changes
586  *
587  * Revision 1.8 2003/08/15 08:37:40 justb
588  * fix/add Copyright+LGPL file headers and footers
589  *
590  *
591  */
Popular Tags