├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── client └── ajax-pushlet-client.js ├── demo ├── UserUnicasttDemo.war └── UserUnicasttDemo │ ├── .classpath │ ├── .project │ ├── .settings │ ├── .jsdtscope │ ├── org.eclipse.jdt.core.prefs │ ├── org.eclipse.wst.common.component │ ├── org.eclipse.wst.common.project.facet.core.xml │ ├── org.eclipse.wst.jsdt.ui.superType.container │ └── org.eclipse.wst.jsdt.ui.superType.name │ ├── WebRoot │ ├── META-INF │ │ └── MANIFEST.MF │ ├── WEB-INF │ │ ├── classes │ │ │ ├── log4j.properties │ │ │ ├── pushlet.properties │ │ │ ├── servlet │ │ │ │ └── MsgPushServlet.class │ │ │ └── sources.properties │ │ ├── lib │ │ │ ├── log4j.jar │ │ │ └── pushlet-userunicast.jar │ │ └── web.xml │ ├── index.jsp │ ├── js │ │ └── ajax-pushlet-client.js │ └── publish.jsp │ └── src │ ├── log4j.properties │ ├── pushlet.properties │ ├── servlet │ └── MsgPushServlet.java │ └── sources.properties ├── doc ├── readme-en.md └── readme-zh.md ├── lib ├── log4j.jar └── pushlet-userunicast.jar ├── readme.md ├── resource ├── log4j.properties ├── pushlet.properties └── sources.properties └── src ├── nl └── justobjects │ └── pushlet │ ├── Version.java │ ├── client │ ├── PushletClient.java │ ├── PushletClient.java.patch │ └── PushletClientListener.java │ ├── core │ ├── BrowserAdapter.java │ ├── ClientAdapter.java │ ├── Command.java │ ├── Config.java │ ├── ConfigDefs.java │ ├── Controller.java │ ├── Dispatcher.java │ ├── Event.java │ ├── EventParser.java │ ├── EventPullSource.java │ ├── EventQueue.java │ ├── EventSource.java │ ├── EventSourceManager.java │ ├── Protocol.java │ ├── SerializedAdapter.java │ ├── Session.java │ ├── SessionManager.java │ ├── Subscriber.java │ ├── Subscription.java │ └── XMLAdapter.java │ ├── servlet │ └── Pushlet.java │ ├── test │ ├── PushletApplet.java │ ├── PushletPingApplication.java │ ├── SimpleListener.java │ ├── StressTester.java │ ├── TestEventPullSources.java │ └── TestEventPushSources.java │ └── util │ ├── DefaultLogger.java │ ├── Log.java │ ├── Log4jLogger.java │ ├── PushletException.java │ ├── PushletLogger.java │ ├── Rand.java │ ├── Servlets.java │ └── Sys.java └── 修改说明.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ['http://www.easyproject.cn/donation'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # ========================= 18 | # Operating System Files 19 | # ========================= 20 | 21 | # OSX 22 | # ========================= 23 | 24 | .DS_Store 25 | .AppleDouble 26 | .LSOverride 27 | 28 | # Icon must ends with two \r. 29 | Icon 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | -------------------------------------------------------------------------------- /demo/UserUnicasttDemo.war: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushelp/Pushlet-UserUnicast/b423f8e1cde4ae761d16cf55d732a16d5d540d1a/demo/UserUnicasttDemo.war -------------------------------------------------------------------------------- /demo/UserUnicasttDemo/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /demo/UserUnicasttDemo/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | pushletDemo3 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.wst.jsdt.core.javascriptValidator 10 | 11 | 12 | 13 | 14 | org.eclipse.jdt.core.javabuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.wst.common.project.facet.core.builder 20 | 21 | 22 | 23 | 24 | org.eclipse.wst.validation.validationbuilder 25 | 26 | 27 | 28 | 29 | com.genuitec.eclipse.j2eedt.core.DeploymentDescriptorValidator 30 | 31 | 32 | 33 | 34 | com.genuitec.eclipse.ast.deploy.core.DeploymentBuilder 35 | 36 | 37 | 38 | 39 | 40 | org.eclipse.jem.workbench.JavaEMFNature 41 | org.eclipse.wst.common.modulecore.ModuleCoreNature 42 | org.eclipse.wst.common.project.facet.core.nature 43 | org.eclipse.jdt.core.javanature 44 | org.eclipse.wst.jsdt.core.jsNature 45 | 46 | 47 | -------------------------------------------------------------------------------- /demo/UserUnicasttDemo/.settings/.jsdtscope: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /demo/UserUnicasttDemo/.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 4 | org.eclipse.jdt.core.compiler.compliance=1.6 5 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 6 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 7 | org.eclipse.jdt.core.compiler.source=1.6 8 | -------------------------------------------------------------------------------- /demo/UserUnicasttDemo/.settings/org.eclipse.wst.common.component: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /demo/UserUnicasttDemo/.settings/org.eclipse.wst.common.project.facet.core.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /demo/UserUnicasttDemo/.settings/org.eclipse.wst.jsdt.ui.superType.container: -------------------------------------------------------------------------------- 1 | org.eclipse.wst.jsdt.launching.baseBrowserLibrary -------------------------------------------------------------------------------- /demo/UserUnicasttDemo/.settings/org.eclipse.wst.jsdt.ui.superType.name: -------------------------------------------------------------------------------- 1 | Window -------------------------------------------------------------------------------- /demo/UserUnicasttDemo/WebRoot/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Class-Path: 3 | 4 | -------------------------------------------------------------------------------- /demo/UserUnicasttDemo/WebRoot/WEB-INF/classes/log4j.properties: -------------------------------------------------------------------------------- 1 | # Sample log4j config, adapt to your own logging needs 2 | # $Id: log4j.properties,v 1.1 2007/12/07 12:57:40 justb Exp $ 3 | # 4 | # ***** Set root logger level to WARN and its two appenders to stdout and R. 5 | log4j.rootLogger=warn, stdout 6 | 7 | # ***** stdout is set to be a ConsoleAppender. 8 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 9 | # ***** stdout uses PatternLayout. 10 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 11 | # ***** Pattern to output the caller's file name and line number. 12 | log4j.appender.stdout.layout.ConversionPattern=%d{yy-MM-dd@HH:mm:ss} %-5p (pushlet) - %m%n 13 | 14 | # ***** R is set to be a RollingFileAppender. 15 | # log4j.appender.R=org.apache.log4j.RollingFileAppender 16 | # log4j.appender.R.File=/Users/just/example.log 17 | # ***** Max file size is set to 100KB 18 | # log4j.appender.R.MaxFileSize=100KB 19 | # ***** Keep one backup file 20 | # log4j.appender.R.MaxBackupIndex=1 21 | # ***** R uses PatternLayout. 22 | # log4j.appender.R.layout=org.apache.log4j.PatternLayout 23 | # log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n 24 | -------------------------------------------------------------------------------- /demo/UserUnicasttDemo/WebRoot/WEB-INF/classes/pushlet.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Pushlet configuration. 3 | # Place this file in the CLASSPATH (e.g. WEB-INF/classes) or directly under WEB-INF. 4 | # 5 | # $Id: pushlet.properties,v 1.13 2007/12/07 12:57:40 justb Exp $ 6 | # 7 | 8 | # 9 | # 10 | # 11 | config.version=1.0.2 12 | 13 | # 14 | # CLASS FACTORY SPECIFICATION 15 | # 16 | # Change these if you want to override any of the core classes 17 | # within the Pushlet framework with your own custom classes. 18 | # 19 | # Examples: 20 | # - custom SessionManager for authorisation 21 | # - maintain lists of active subjects (topics) 22 | # - send events on subscription 23 | # - plug in custom logging like log4j 24 | # Note that you must maintain the semantics of each class ! 25 | # Below are the default properties for the core classes. 26 | controller.class=nl.justobjects.pushlet.core.Controller 27 | dispatcher.class=nl.justobjects.pushlet.core.Dispatcher 28 | logger.class=nl.justobjects.pushlet.util.Log4jLogger 29 | # logger.class=nl.justobjects.pushlet.util.DefaultLogger 30 | sessionmanager.class=nl.justobjects.pushlet.core.SessionManager 31 | session.class=nl.justobjects.pushlet.core.Session 32 | subscriber.class=nl.justobjects.pushlet.core.Subscriber 33 | subscription.class=nl.justobjects.pushlet.core.Subscription 34 | 35 | # sessionmanager.maxsessions=200 36 | 37 | # 38 | # DISPATCHER 39 | # 40 | 41 | 42 | # TODO: allow properties to be maintained in 43 | # a user dir 44 | # config.redirect=/etc/pushlet.properties 45 | 46 | # 47 | # LOGGING 48 | # 49 | 50 | # log level (trace(6) debug(5) info (4), warn(3), error(2), fatal(1)) 51 | # default is info(4) 52 | log.level=4 53 | 54 | # 55 | # LOCAL EVENT SOURCES 56 | # 57 | 58 | # should local sources be loaded ? 59 | sources.activate=true 60 | 61 | # 62 | # SESSION 63 | # 64 | 65 | 66 | # algoritm to generate session key: 67 | # values: "randomstring" (default) or "uuid". 68 | # session.id.generation=uuid 69 | session.id.generation=randomstring 70 | 71 | # length of generated session key when using "randomstring" generation 72 | session.id.size=10 73 | 74 | # Overall session lease time in minutes 75 | # Mainly used for clients that do not perform 76 | # listening, e.g. when publishing only. 77 | session.timeout.mins=5 78 | 79 | # 80 | # EVENT QUEUE 81 | # 82 | # Properties for per-client data event queue 83 | 84 | # Size for 85 | queue.size=24 86 | queue.read.timeout.millis=20000 87 | queue.write.timeout.millis=20 88 | 89 | # 90 | # LISTENING MODE 91 | # 92 | 93 | # You may force all clients to use pull mode 94 | # for scalability 95 | listen.force.pull.all=false 96 | 97 | # 98 | # Comma-separated list of User Agent substrings. 99 | # Force these browsers to use pull mode, since they 100 | # don't support JS streaming, matching is done using 101 | # String.indexOf() with lowercased agent strings 102 | # use multiple criteria with &. 103 | # 104 | listen.force.pull.agents=safari 105 | 106 | # 107 | # PULL MODE 108 | # 109 | 110 | # time server should wait on refresing pull client 111 | pull.refresh.timeout.millis=45000 112 | 113 | # minimum/maximum wait time client should wait before refreshing 114 | # server provides a random time between these values 115 | pull.refresh.wait.min.millis=2000 116 | pull.refresh.wait.max.millis=6000 117 | 118 | # 119 | # POLL MODE 120 | # 121 | 122 | # time server should wait on refresing poll client 123 | poll.refresh.timeout.millis=60000 124 | 125 | # minimum/maximum wait time client should wait before refreshing 126 | # server provides a random time between these values 127 | poll.refresh.wait.min.millis=6000 128 | poll.refresh.wait.max.millis=10000 129 | unicast.type=first 130 | -------------------------------------------------------------------------------- /demo/UserUnicasttDemo/WebRoot/WEB-INF/classes/servlet/MsgPushServlet.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushelp/Pushlet-UserUnicast/b423f8e1cde4ae761d16cf55d732a16d5d540d1a/demo/UserUnicasttDemo/WebRoot/WEB-INF/classes/servlet/MsgPushServlet.class -------------------------------------------------------------------------------- /demo/UserUnicasttDemo/WebRoot/WEB-INF/classes/sources.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Properties file for EventSource objects to be instantiated. 3 | # 4 | # Place this file in the CLASSPATH (e.g. WEB-INF/classes) or directly under WEB-INF. 5 | # 6 | # $Id: sources.properties,v 1.2 2007/11/10 14:12:16 justb Exp $ 7 | # 8 | # Each EventSource is defined as = 9 | # 1. should be unique within this file but may be any name 10 | # 2. is the full class name 11 | # 12 | # 13 | # Define Pull Sources here. These classes must be derived from 14 | # nl.justobjects.pushlet.core.EventPullSource 15 | # Inner classes are separated with a $ sign from the outer class. 16 | #source1=nl.justobjects.pushlet.test.TestEventPullSources$TemperatureEventPullSource 17 | #source2=nl.justobjects.pushlet.test.TestEventPullSources$SystemStatusEventPullSource 18 | #source3=nl.justobjects.pushlet.test.TestEventPullSources$PushletStatusEventPullSource 19 | #source4=nl.justobjects.pushlet.test.TestEventPullSources$AEXStocksEventPullSource 20 | #source5=nl.justobjects.pushlet.test.TestEventPullSources$WebPresentationEventPullSource 21 | #source6=nl.justobjects.pushlet.test.TestEventPullSources$PingEventPullSource 22 | 23 | # TO BE DONE IN NEXT VERSION 24 | # define Push Sources here. These must implement the interface 25 | # nl.justobjects.pushlet.core.EventSource 26 | 27 | -------------------------------------------------------------------------------- /demo/UserUnicasttDemo/WebRoot/WEB-INF/lib/log4j.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushelp/Pushlet-UserUnicast/b423f8e1cde4ae761d16cf55d732a16d5d540d1a/demo/UserUnicasttDemo/WebRoot/WEB-INF/lib/log4j.jar -------------------------------------------------------------------------------- /demo/UserUnicasttDemo/WebRoot/WEB-INF/lib/pushlet-userunicast.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushelp/Pushlet-UserUnicast/b423f8e1cde4ae761d16cf55d732a16d5d540d1a/demo/UserUnicasttDemo/WebRoot/WEB-INF/lib/pushlet-userunicast.jar -------------------------------------------------------------------------------- /demo/UserUnicasttDemo/WebRoot/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | pushlet 5 | 6 | nl.justobjects.pushlet.servlet.Pushlet 7 | 8 | 1 9 | 10 | 11 | pushlet 12 | /pushlet.srv 13 | 14 | 15 | MsgPushServlet 16 | servlet.MsgPushServlet 17 | 18 | 19 | MsgPushServlet 20 | /servlet/MsgPushServlet 21 | 22 | -------------------------------------------------------------------------------- /demo/UserUnicasttDemo/WebRoot/index.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> 2 | <% 3 | String path = request.getContextPath(); 4 | String basePath = request.getScheme() + "://" 5 | + request.getServerName() + ":" + request.getServerPort() 6 | + path + "/"; 7 | %> 8 | 9 | 10 | 11 | 12 | 13 | 14 | 用户接收消息页面 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 38 | 39 | 40 | 41 |

连接服务器中....

42 |
43 |
44 |
45 |
46 | 后台推送消息页面 47 | 48 | 49 | -------------------------------------------------------------------------------- /demo/UserUnicasttDemo/WebRoot/publish.jsp: -------------------------------------------------------------------------------- 1 | <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> 2 | <% 3 | String path = request.getContextPath(); 4 | String basePath = request.getScheme() + "://" 5 | + request.getServerName() + ":" + request.getServerPort() 6 | + path + "/"; 7 | %> 8 | 9 | 10 | 11 | 12 | 13 | 14 | 发布推送消息 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | 25 | 26 | 27 |

发布需要推送的信息:

28 |
29 |

30 | 推送的用户userId: 31 |

32 |

33 | 推送内容: 34 |

35 |

36 | 推送类型: 37 | 38 | 39 | 40 | 41 | 42 | 43 |

44 | 45 |
46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /demo/UserUnicasttDemo/src/log4j.properties: -------------------------------------------------------------------------------- 1 | # Sample log4j config, adapt to your own logging needs 2 | # $Id: log4j.properties,v 1.1 2007/12/07 12:57:40 justb Exp $ 3 | # 4 | # ***** Set root logger level to WARN and its two appenders to stdout and R. 5 | log4j.rootLogger=warn, stdout 6 | 7 | # ***** stdout is set to be a ConsoleAppender. 8 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 9 | # ***** stdout uses PatternLayout. 10 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 11 | # ***** Pattern to output the caller's file name and line number. 12 | log4j.appender.stdout.layout.ConversionPattern=%d{yy-MM-dd@HH:mm:ss} %-5p (pushlet) - %m%n 13 | 14 | # ***** R is set to be a RollingFileAppender. 15 | # log4j.appender.R=org.apache.log4j.RollingFileAppender 16 | # log4j.appender.R.File=/Users/just/example.log 17 | # ***** Max file size is set to 100KB 18 | # log4j.appender.R.MaxFileSize=100KB 19 | # ***** Keep one backup file 20 | # log4j.appender.R.MaxBackupIndex=1 21 | # ***** R uses PatternLayout. 22 | # log4j.appender.R.layout=org.apache.log4j.PatternLayout 23 | # log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n 24 | -------------------------------------------------------------------------------- /demo/UserUnicasttDemo/src/pushlet.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Pushlet configuration. 3 | # Place this file in the CLASSPATH (e.g. WEB-INF/classes) or directly under WEB-INF. 4 | # 5 | # $Id: pushlet.properties,v 1.13 2007/12/07 12:57:40 justb Exp $ 6 | # 7 | 8 | # 9 | # 10 | # 11 | config.version=1.0.2 12 | 13 | # 14 | # CLASS FACTORY SPECIFICATION 15 | # 16 | # Change these if you want to override any of the core classes 17 | # within the Pushlet framework with your own custom classes. 18 | # 19 | # Examples: 20 | # - custom SessionManager for authorisation 21 | # - maintain lists of active subjects (topics) 22 | # - send events on subscription 23 | # - plug in custom logging like log4j 24 | # Note that you must maintain the semantics of each class ! 25 | # Below are the default properties for the core classes. 26 | controller.class=nl.justobjects.pushlet.core.Controller 27 | dispatcher.class=nl.justobjects.pushlet.core.Dispatcher 28 | logger.class=nl.justobjects.pushlet.util.Log4jLogger 29 | # logger.class=nl.justobjects.pushlet.util.DefaultLogger 30 | sessionmanager.class=nl.justobjects.pushlet.core.SessionManager 31 | session.class=nl.justobjects.pushlet.core.Session 32 | subscriber.class=nl.justobjects.pushlet.core.Subscriber 33 | subscription.class=nl.justobjects.pushlet.core.Subscription 34 | 35 | # sessionmanager.maxsessions=200 36 | 37 | # 38 | # DISPATCHER 39 | # 40 | 41 | 42 | # TODO: allow properties to be maintained in 43 | # a user dir 44 | # config.redirect=/etc/pushlet.properties 45 | 46 | # 47 | # LOGGING 48 | # 49 | 50 | # log level (trace(6) debug(5) info (4), warn(3), error(2), fatal(1)) 51 | # default is info(4) 52 | log.level=4 53 | 54 | # 55 | # LOCAL EVENT SOURCES 56 | # 57 | 58 | # should local sources be loaded ? 59 | sources.activate=true 60 | 61 | # 62 | # SESSION 63 | # 64 | 65 | 66 | # algoritm to generate session key: 67 | # values: "randomstring" (default) or "uuid". 68 | # session.id.generation=uuid 69 | session.id.generation=randomstring 70 | 71 | # length of generated session key when using "randomstring" generation 72 | session.id.size=10 73 | 74 | # Overall session lease time in minutes 75 | # Mainly used for clients that do not perform 76 | # listening, e.g. when publishing only. 77 | session.timeout.mins=5 78 | 79 | # 80 | # EVENT QUEUE 81 | # 82 | # Properties for per-client data event queue 83 | 84 | # Size for 85 | queue.size=24 86 | queue.read.timeout.millis=20000 87 | queue.write.timeout.millis=20 88 | 89 | # 90 | # LISTENING MODE 91 | # 92 | 93 | # You may force all clients to use pull mode 94 | # for scalability 95 | listen.force.pull.all=false 96 | 97 | # 98 | # Comma-separated list of User Agent substrings. 99 | # Force these browsers to use pull mode, since they 100 | # don't support JS streaming, matching is done using 101 | # String.indexOf() with lowercased agent strings 102 | # use multiple criteria with &. 103 | # 104 | listen.force.pull.agents=safari 105 | 106 | # 107 | # PULL MODE 108 | # 109 | 110 | # time server should wait on refresing pull client 111 | pull.refresh.timeout.millis=45000 112 | 113 | # minimum/maximum wait time client should wait before refreshing 114 | # server provides a random time between these values 115 | pull.refresh.wait.min.millis=2000 116 | pull.refresh.wait.max.millis=6000 117 | 118 | # 119 | # POLL MODE 120 | # 121 | 122 | # time server should wait on refresing poll client 123 | poll.refresh.timeout.millis=60000 124 | 125 | # minimum/maximum wait time client should wait before refreshing 126 | # server provides a random time between these values 127 | poll.refresh.wait.min.millis=6000 128 | poll.refresh.wait.max.millis=10000 129 | unicast.type=first 130 | -------------------------------------------------------------------------------- /demo/UserUnicasttDemo/src/servlet/MsgPushServlet.java: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushelp/Pushlet-UserUnicast/b423f8e1cde4ae761d16cf55d732a16d5d540d1a/demo/UserUnicasttDemo/src/servlet/MsgPushServlet.java -------------------------------------------------------------------------------- /demo/UserUnicasttDemo/src/sources.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Properties file for EventSource objects to be instantiated. 3 | # 4 | # Place this file in the CLASSPATH (e.g. WEB-INF/classes) or directly under WEB-INF. 5 | # 6 | # $Id: sources.properties,v 1.2 2007/11/10 14:12:16 justb Exp $ 7 | # 8 | # Each EventSource is defined as = 9 | # 1. should be unique within this file but may be any name 10 | # 2. is the full class name 11 | # 12 | # 13 | # Define Pull Sources here. These classes must be derived from 14 | # nl.justobjects.pushlet.core.EventPullSource 15 | # Inner classes are separated with a $ sign from the outer class. 16 | #source1=nl.justobjects.pushlet.test.TestEventPullSources$TemperatureEventPullSource 17 | #source2=nl.justobjects.pushlet.test.TestEventPullSources$SystemStatusEventPullSource 18 | #source3=nl.justobjects.pushlet.test.TestEventPullSources$PushletStatusEventPullSource 19 | #source4=nl.justobjects.pushlet.test.TestEventPullSources$AEXStocksEventPullSource 20 | #source5=nl.justobjects.pushlet.test.TestEventPullSources$WebPresentationEventPullSource 21 | #source6=nl.justobjects.pushlet.test.TestEventPullSources$PingEventPullSource 22 | 23 | # TO BE DONE IN NEXT VERSION 24 | # define Push Sources here. These must implement the interface 25 | # nl.justobjects.pushlet.core.EventSource 26 | 27 | -------------------------------------------------------------------------------- /doc/readme-en.md: -------------------------------------------------------------------------------- 1 | # pushlet-UserUnicast Manual 2 | 3 | --------------- 4 | 5 | pushlet-UserUnicast is based on a modified extension `JAVA comet` pushlet after implementation framework. Major expansion of the `unicast` real demand push function. 6 | 7 | > The method `unicast` pushlet can push data to the specified session (` SessionID`), the data is pushed to the specified user, since the `SessionID` can be randomly generated, can not be associated with a user, it is not according to the service field (such as the user ID, user name) and so pushed to the specified user. 8 | 9 | **Core implementation features:** 10 | 11 | - Push messages directly to a specified user by user ID (user name, primary key ID and other characters can be identified) 12 | - Supports multi-user connections push and control: `all`,` first`, `last` three types of switching 13 | - use `Event` be` setField () ` no need to convert the character encoding to support other character, 14 | - Easy to use 15 | 16 | **Explanation:** 17 | 18 | - Read this document, use pushlet-UserUnicast, need to master pushlet basis of lack of understanding pushlet unicast 19 | 20 | - This document includes only: How to use pushlet-UserUnicast achieve the specified user demand for data 21 | 22 | - This document does not include: Introduction pushlet's, pushlet use, pushlet the API. For more information, please consult other documentation. 23 | 24 | **Note:** 25 | 26 | - Due to restrictions push mechanism and the technology itself, `first` and` last` push is not absolutely accurate. This is because: After the user disconnects, and can not immediately detect and remove users from the queue (with a certain time lag), so if a user is disconnected immediately `first` or` last` push, might pushed to disconnected users. But in the time difference within the allowable range is safe, but `all` way when push absolutely safe. 27 | 28 | ## pushlet-userunicast API: 29 | 30 | ### Client: 31 | - `PL.userId = "userId"`: 32 | Use `PL.userId` attribute specifies the string that identifies the client, the server-side push based on this data to identify the client. Use the `; in` PL._init (). Such as: 33 | ```JS 34 | PL._init(); 35 | PL.userId="JAY"; 36 | ``` 37 | 38 | ### Server: 39 | 40 | - `Dispatcher.getInstance().unicastUserId(Event, userId); `: Push data to the specified user ID client. 41 | - `Event`: Release event object and data 42 | - `userId`: User ID specified in the corresponding client `PL.userId` 43 | For example: 44 | ```JAVA 45 | String userId="JAY"; 46 | //unicastUserId Pushed to the specified user 47 | Dispatcher.getInstance().unicastUserId(event, userId); 48 | ``` 49 | 50 | - `Dispatcher.getInstance().unicastUserId(Event, userId, unicastType); `: In accordance with the specified user multiple connections unicastType push type, push data to the specified user ID client. 51 | - `Event`: Event objects and data release 52 | - `userId`: User ID specified in the corresponding client `PL.userId` 53 | - `unicastType`: Multi-user connections push type, optional value `all`,` first`, `last`(See Role: Multi-user connections configuration parameter). You can use constants, said: `Sys.USERUNICAST_ALL`, `Sys.USERUNICAST_First`, `Sys.USERUNICAST_Last` 54 | For example: 55 | ```JAVA 56 | String userId="JAY"; 57 | //unicastUserId Push all connections to the specified user 58 | Dispatcher.getInstance().unicastUserId(event, userId, "all"); 59 | ``` 60 | 61 | ### Multi-user connection configuration parameters: 62 | 63 | When multiple clients to connect simultaneously use the same user ID `PL.userId` can be more connected to the push type parameters by` unicast.type` user, `unicastUserId (Event, userId)` method uses multiple connections that value as the user push the way. 64 | 65 | In `pushlet.properties` can configure` unicast.type` parameters, optional value: 66 | - `all`: The default value, the presence of the same user multiple client connections, the client connections to all push messages. 67 | - `first`: Only to the first client requests a connection push message, after connecting clients can not receive the message (when no other users connected to the same name, only to join the push, existing user connections, after connecting the user does not push). 68 | - `last`: Only client connection requests to the last push message. 69 | 70 | 71 | 72 | ## pushlet-userunicast Users push the use of steps and examples: 73 | 74 | 1. Jar package introduced for the project under lib: 75 | **pushlet-userunicast.jar** 76 | **log4j.jar** 77 | 78 | 2. Join the configuration file resource under the `src` or` WEB-INF` in: 79 | **log4j.properties** 80 | **pushlet.properties** 81 | **sources.properties** 82 | 83 | 3. Need to subscribe to an event added to the Web project client under the client JS file: 84 | **ajax-pushlet-client.js** 85 | 86 | 4. In `web.xml` core Servlet controller configuration` pushlet` 87 | ```XML 88 | 89 | pushlet 90 | 91 | nl.justobjects.pushlet.servlet.Pushlet 92 | 93 | 1 94 | 95 | 96 | pushlet 97 | /pushlet.srv 98 | 99 | ``` 100 | 101 | 5. Clients subscribe to receive messages index.jsp 102 | Use `PL.userId` attribute specifies the string that identifies the client, server-side push data in accordance with this identity to the client 103 | ```JS 104 | 105 | 106 | 107 | Users receive a message page 108 | 109 | 110 | 126 | 127 | 128 | 129 |

Please wait, Server Connecting....

130 |
131 |
132 |
133 |
134 | Push back page news 135 | 136 | 137 | ``` 138 | 139 | 6. Servlet server-side push test data 140 | Use `Dispatcher.getInstance () unicastUserId (Event, userId user ID);` push data to the specified client user ID, `userId` user ID corresponding `PL.userId`. 141 | ```JAVA 142 | package servlet; 143 | 144 | import java.io.IOException; 145 | 146 | import javax.servlet.ServletException; 147 | import javax.servlet.http.HttpServlet; 148 | import javax.servlet.http.HttpServletRequest; 149 | import javax.servlet.http.HttpServletResponse; 150 | 151 | import nl.justobjects.pushlet.core.Dispatcher; 152 | import nl.justobjects.pushlet.core.Event; 153 | 154 | public class MsgPushServlet extends HttpServlet { 155 | 156 | @Override 157 | protected void service(HttpServletRequest req, HttpServletResponse resp) 158 | throws ServletException, IOException { 159 | req.setCharacterEncoding("utf-8"); 160 | // Push message 161 | String msg=req.getParameter("msg"); 162 | // String userId="JAY"; 163 | // Push users 164 | String userId=req.getParameter("userId"); 165 | // Push-user multi-way connection 166 | String type=req.getParameter("type"); 167 | 168 | // Event objects and data 169 | Event event = Event.createDataEvent("/push/hello"); 170 | event.setField("msg", msg); //Chinese do not need to be converted to ISO-8859-1 171 | 172 | // According unicast.type push parameter values ​​to the specified user pushlet.properties 173 | // Dispatcher.getInstance().unicastUserId(event,userId); 174 | 175 | // Pushed to the specified user from the specified type type 176 | Dispatcher.getInstance().unicastUserId(event, userId, type); 177 | } 178 | } 179 | ``` 180 | The servlet web.xml configuration 181 | ```XML 182 | 183 | MsgPushServlet 184 | servlet.MsgPushServlet 185 | 186 | 187 | 188 | MsgPushServlet 189 | /servlet/MsgPushServlet 190 | 191 | ``` 192 | 193 | 7. Call Servlet, publish pages publish.jsp push message 194 | ```HTML 195 | 196 | 197 | 198 | Published push message 199 | 200 | 201 | 202 |

Published need to push the message:

203 |
204 |

205 | push userId: 206 |

207 |

208 | push message: 209 |

210 |

211 | push type: 212 | 213 | 214 | 215 | 216 | 217 | 218 |

219 | 220 |
221 | 222 | 223 | 224 | 225 | ``` 226 | 227 | 228 | 229 | 230 | ## End 231 | 232 | [Demo online](http://www.easyproject.cn/easyunicast/en/index.jsp#demo 'Demo online') 233 | 234 | [Comments](http://www.easyproject.cn/easyunicast/en/index.jsp#about 'Comments') 235 | 236 | If you have more comments, suggestions or ideas, please contact me. 237 | 238 | 239 | Contact, feedback, customization, training Email: 240 | 241 |

242 |

243 | 244 | 245 | 246 | 247 | 248 | 249 |
250 |

251 | 252 | [http://www.easyproject.cn](http://www.easyproject.cn "EasyProject Home") -------------------------------------------------------------------------------- /doc/readme-zh.md: -------------------------------------------------------------------------------- 1 | # pushlet-UserUnicast 使用手册 2 | 3 | --------------- 4 | 5 | pushlet-UserUnicast 是一个基于pushlet修改扩展之后的`JAVA comet`实现框架。主要扩展实了`unicast`点播推送功能。 6 | 7 | > pushlet中的 `unicast` 方法可以实现向指定会话(`SessionID`)推送数据,将数据推送到指定用户,可以由于该 `SessionID` 是随机生成,无法与用户关联,所以无法根据业务字段(如用户ID,用户名)等推送给指定用户。 8 | 9 | **核心实现特点:** 10 | 11 | - 可以通过用户标识(用户名,主键ID等字符标识均可)向指定用户直接推送消息 12 | - 支持用户多连接的推送和控制:`all`, `first`, `last` 三种类型切换 13 | - 支持中文,使用 `Event` 进行`setField()`时无需转换字符编码 14 | - 简单易用 15 | 16 | **说明:** 17 | 18 | - 阅读本文档、使用 pushlet-UserUnicast,需要掌握 pushlet 基础,了解 pushlet unicast 的不足 19 | 20 | - 本文档仅包括:如何利用 pushlet-UserUnicast 实现对指定用户点播数据 21 | 22 | - 本文档不包括:pushlet的介绍、pushlet使用方法、pushlet的API。如需了解,请查阅其他文档。 23 | 24 | **注意:** 25 | 26 | - 由于推送机制和技术本身的限制,`first`和`last`推送并非是绝对精确的。这是因为:用户断开后,并不能立即检测到并从队列移除用户(有一定时间差),所以如果在某个用户断开连接后,立即进行`first`或`last`推送,可能会推送给已断开的用户。但在时间差允许范围内是安全的,而`all`方式推送时绝对安全的。 27 | 28 | ## pushlet-userunicast API: 29 | 30 | ### 客户端: 31 | - `PL.userId = "userId"`: 32 | 使用`PL.userId`属性指定客户端的字符串标识,服务器端根据此标识推送数据到该客户端。在 `PL._init();` 后使用。例如: 33 | ```JS 34 | PL._init(); 35 | PL.userId="JAY"; 36 | ``` 37 | 38 | ### 服务端: 39 | 40 | - `Dispatcher.getInstance().unicastUserId(Event, userId); `:推送数据到指定用户标识的客户端。 41 | - `Event`: 发布的事件对象和数据 42 | - `userId`: 用户标识,对应客户端指定的 `PL.userId` 43 | 例如: 44 | ```JAVA 45 | String userId="JAY"; 46 | //unicastUserId 推送给指定用户 47 | Dispatcher.getInstance().unicastUserId(event, userId); 48 | ``` 49 | 50 | - `Dispatcher.getInstance().unicastUserId(Event, userId, unicastType); `:按照unicastType指定的用户多连接推送类型,推送数据到指定用户标识的客户端。 51 | - `Event`: 发布的事件对象和数据 52 | - `userId`: 用户标识,对应客户端指定的 `PL.userId` 53 | - `unicastType`: 用户多连接推送类型,可选值为 `all`, `first`, `last`(作用参见:用户多连接配置参数)。可使用常量表示:`Sys.USERUNICAST_ALL`, `Sys.USERUNICAST_First`, `Sys.USERUNICAST_Last` 54 | 例如: 55 | ```JAVA 56 | String userId="JAY"; 57 | //unicastUserId 推送给指定用户的所有连接 58 | Dispatcher.getInstance().unicastUserId(event, userId, "all"); 59 | ``` 60 | 61 | ### 用户多连接配置参数: 62 | 63 | 当多个客户端连接同时使用相同`PL.userId`用户标识时,可以通过 `unicast.type` 参数用户多连接推送类型,`unicastUserId(Event, userId)`方法会使用该值作为用户多连接推送方式。 64 | 65 | 在 `pushlet.properties` 中可以配置 `unicast.type` 参数,可选值为: 66 | - `all`: 默认值,同一个用户存在多个客户端连接时,向全部客户端连接都推送消息。 67 | - `first`: 仅向最先请求连接的客户端推送消息,后连接的客户端无法接收到消息(当没有其他同名用户连接时,才加入推送,已有用户连接,后连接用户不推送)。 68 | - `last`: 仅向最后请求连接的客户端推消息。 69 | 70 | 71 | 72 | ## pushlet-userunicast 用户推送使用步骤和示例: 73 | 74 | 1. 为项目引入lib下的jar包: 75 | **pushlet-userunicast.jar** 76 | **log4j.jar** 77 | 78 | 2. 在 `src` 或 `WEB-INF` 中加入resource下的配置文件: 79 | **log4j.properties** 80 | **pushlet.properties** 81 | **sources.properties** 82 | 83 | 3. 在需要订阅事件的 Web 项目中加入client下的客户端JS文件: 84 | **ajax-pushlet-client.js** 85 | 86 | 4. 在 `web.xml` 配置 `pushlet` 核心Servlet控制器 87 | ```XML 88 | 89 | pushlet 90 | 91 | nl.justobjects.pushlet.servlet.Pushlet 92 | 93 | 1 94 | 95 | 96 | pushlet 97 | /pushlet.srv 98 | 99 | ``` 100 | 101 | 5. 客户端订阅接收消息 index.jsp 102 | 使用`PL.userId`属性指定客户端的字符串标识,服务器端根据此标识推送数据到该客户端 103 | ```JS 104 | 105 | 106 | 107 | 用户接收消息页面 108 | 109 | 110 | 126 | 127 | 128 | 129 |

请等待,连接服务器中....

130 |
131 |
132 |
133 |
134 | 后台推送消息页面 135 | 136 | 137 | ``` 138 | 139 | 6. 服务器端推送数据的测试Servlet 140 | 使用 `Dispatcher.getInstance().unicastUserId(Event, userId用户标识); ` 推送数据到指定用户标识的客户端,`userId用户标识` 对应 `PL.userId` 。 141 | ```JAVA 142 | package servlet; 143 | 144 | import java.io.IOException; 145 | 146 | import javax.servlet.ServletException; 147 | import javax.servlet.http.HttpServlet; 148 | import javax.servlet.http.HttpServletRequest; 149 | import javax.servlet.http.HttpServletResponse; 150 | 151 | import nl.justobjects.pushlet.core.Dispatcher; 152 | import nl.justobjects.pushlet.core.Event; 153 | 154 | public class MsgPushServlet extends HttpServlet { 155 | 156 | @Override 157 | protected void service(HttpServletRequest req, HttpServletResponse resp) 158 | throws ServletException, IOException { 159 | req.setCharacterEncoding("utf-8"); 160 | // 推送消息 161 | String msg=req.getParameter("msg"); 162 | // String userId="JAY"; 163 | // 推送用户 164 | String userId=req.getParameter("userId"); 165 | // 用户多连接推送方式 166 | String type=req.getParameter("type"); 167 | 168 | // 事件对象和数据 169 | Event event = Event.createDataEvent("/push/hello"); 170 | event.setField("msg", msg); //中文无需转换为ISO-8859-1 171 | 172 | // 根据pushlet.properties的unicast.type参数值推送给指定用户 173 | // Dispatcher.getInstance().unicastUserId(event,userId); 174 | 175 | // 根据指定type类型推送给指定用户 176 | Dispatcher.getInstance().unicastUserId(event, userId, type); 177 | } 178 | } 179 | ``` 180 | web.xml的servlet配置 181 | ```XML 182 | 183 | MsgPushServlet 184 | servlet.MsgPushServlet 185 | 186 | 187 | 188 | MsgPushServlet 189 | /servlet/MsgPushServlet 190 | 191 | ``` 192 | 193 | 7. 调用Servlet,发布推送消息的页面 publish.jsp 194 | ```HTML 195 | 196 | 197 | 198 | 发布推送消息 199 | 200 | 201 | 202 |

发布需要推送的信息:

203 |
204 |

205 | 推送的用户userId: 206 |

207 |

208 | 推送内容: 209 |

210 |

211 | 推送类型: 212 | 213 | 214 | 215 | 216 | 217 | 218 |

219 | 220 |
221 | 222 | 223 | 224 | 225 | ``` 226 | 227 | 228 | 229 | 230 | ## 结束 231 | 232 | [在线Demo](http://www.easyproject.cn/easyunicast/zh-cn/index.jsp#demo '在线 Demo') 233 | 234 | [留言评论](http://www.easyproject.cn/easyocr/zh-cn/index.jsp#about '留言评论') 235 | 236 | 如果您有更好意见,建议或想法,请联系我。 237 | 238 | 239 | 联系、反馈、定制、培训 Email: 240 | 241 | 242 |

243 | 支付宝钱包扫一扫捐助: 244 |

245 |

246 | 247 | 支付宝钱包扫一扫捐助 248 | 249 | 250 | [http://www.easyproject.cn](http://www.easyproject.cn "EasyProject Home") -------------------------------------------------------------------------------- /lib/log4j.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushelp/Pushlet-UserUnicast/b423f8e1cde4ae761d16cf55d732a16d5d540d1a/lib/log4j.jar -------------------------------------------------------------------------------- /lib/pushlet-userunicast.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushelp/Pushlet-UserUnicast/b423f8e1cde4ae761d16cf55d732a16d5d540d1a/lib/pushlet-userunicast.jar -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # pushlet-UserUnicast 2 | 3 | --------------- 4 | 5 | 基于pushlet的用户点播JAVA comet实现框架。 6 | 7 | - ### [中文](doc/readme-zh.md) 8 | 9 | 10 | JAVA COMET UserUnicast framework based on Pushlet. 11 | 12 | - ### [English](doc/readme-en.md) 13 | 14 | 15 | ## 16 | 17 | 18 | 如果您有更好意见,建议或想法,请联系我。 19 | 20 | If you have more comments, suggestions or ideas, please contact me. 21 | 22 | Email: 23 | 24 | [http://www.easyproject.cn](http://www.easyproject.cn "EasyProject Home") -------------------------------------------------------------------------------- /resource/log4j.properties: -------------------------------------------------------------------------------- 1 | # Sample log4j config, adapt to your own logging needs 2 | # $Id: log4j.properties,v 1.1 2007/12/07 12:57:40 justb Exp $ 3 | # 4 | # ***** Set root logger level to WARN and its two appenders to stdout and R. 5 | log4j.rootLogger=warn, stdout 6 | 7 | # ***** stdout is set to be a ConsoleAppender. 8 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 9 | # ***** stdout uses PatternLayout. 10 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 11 | # ***** Pattern to output the caller's file name and line number. 12 | log4j.appender.stdout.layout.ConversionPattern=%d{yy-MM-dd@HH:mm:ss} %-5p (pushlet) - %m%n 13 | 14 | # ***** R is set to be a RollingFileAppender. 15 | # log4j.appender.R=org.apache.log4j.RollingFileAppender 16 | # log4j.appender.R.File=/Users/just/example.log 17 | # ***** Max file size is set to 100KB 18 | # log4j.appender.R.MaxFileSize=100KB 19 | # ***** Keep one backup file 20 | # log4j.appender.R.MaxBackupIndex=1 21 | # ***** R uses PatternLayout. 22 | # log4j.appender.R.layout=org.apache.log4j.PatternLayout 23 | # log4j.appender.R.layout.ConversionPattern=%p %t %c - %m%n 24 | -------------------------------------------------------------------------------- /resource/pushlet.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Pushlet configuration. 3 | # Place this file in the CLASSPATH (e.g. WEB-INF/classes) or directly under WEB-INF. 4 | # 5 | # $Id: pushlet.properties,v 1.13 2007/12/07 12:57:40 justb Exp $ 6 | # 7 | 8 | # 9 | # 10 | # 11 | config.version=1.0.2 12 | 13 | # 14 | # CLASS FACTORY SPECIFICATION 15 | # 16 | # Change these if you want to override any of the core classes 17 | # within the Pushlet framework with your own custom classes. 18 | # 19 | # Examples: 20 | # - custom SessionManager for authorisation 21 | # - maintain lists of active subjects (topics) 22 | # - send events on subscription 23 | # - plug in custom logging like log4j 24 | # Note that you must maintain the semantics of each class ! 25 | # Below are the default properties for the core classes. 26 | controller.class=nl.justobjects.pushlet.core.Controller 27 | dispatcher.class=nl.justobjects.pushlet.core.Dispatcher 28 | logger.class=nl.justobjects.pushlet.util.Log4jLogger 29 | # logger.class=nl.justobjects.pushlet.util.DefaultLogger 30 | sessionmanager.class=nl.justobjects.pushlet.core.SessionManager 31 | session.class=nl.justobjects.pushlet.core.Session 32 | subscriber.class=nl.justobjects.pushlet.core.Subscriber 33 | subscription.class=nl.justobjects.pushlet.core.Subscription 34 | 35 | # sessionmanager.maxsessions=200 36 | 37 | # 38 | # DISPATCHER 39 | # 40 | 41 | 42 | # TODO: allow properties to be maintained in 43 | # a user dir 44 | # config.redirect=/etc/pushlet.properties 45 | 46 | # 47 | # LOGGING 48 | # 49 | 50 | # log level (trace(6) debug(5) info (4), warn(3), error(2), fatal(1)) 51 | # default is info(4) 52 | log.level=4 53 | 54 | # 55 | # LOCAL EVENT SOURCES 56 | # 57 | 58 | # should local sources be loaded ? 59 | sources.activate=true 60 | 61 | # 62 | # SESSION 63 | # 64 | 65 | 66 | # algoritm to generate session key: 67 | # values: "randomstring" (default) or "uuid". 68 | # session.id.generation=uuid 69 | session.id.generation=randomstring 70 | 71 | # length of generated session key when using "randomstring" generation 72 | session.id.size=10 73 | 74 | # Overall session lease time in minutes 75 | # Mainly used for clients that do not perform 76 | # listening, e.g. when publishing only. 77 | session.timeout.mins=5 78 | 79 | # 80 | # EVENT QUEUE 81 | # 82 | # Properties for per-client data event queue 83 | 84 | # Size for 85 | queue.size=24 86 | queue.read.timeout.millis=20000 87 | queue.write.timeout.millis=20 88 | 89 | # 90 | # LISTENING MODE 91 | # 92 | 93 | # You may force all clients to use pull mode 94 | # for scalability 95 | listen.force.pull.all=false 96 | 97 | # 98 | # Comma-separated list of User Agent substrings. 99 | # Force these browsers to use pull mode, since they 100 | # don't support JS streaming, matching is done using 101 | # String.indexOf() with lowercased agent strings 102 | # use multiple criteria with &. 103 | # 104 | listen.force.pull.agents=safari 105 | 106 | # 107 | # PULL MODE 108 | # 109 | 110 | # time server should wait on refresing pull client 111 | pull.refresh.timeout.millis=45000 112 | 113 | # minimum/maximum wait time client should wait before refreshing 114 | # server provides a random time between these values 115 | pull.refresh.wait.min.millis=2000 116 | pull.refresh.wait.max.millis=6000 117 | 118 | # 119 | # POLL MODE 120 | # 121 | 122 | # time server should wait on refresing poll client 123 | poll.refresh.timeout.millis=60000 124 | 125 | # minimum/maximum wait time client should wait before refreshing 126 | # server provides a random time between these values 127 | poll.refresh.wait.min.millis=6000 128 | poll.refresh.wait.max.millis=10000 129 | unicast.type=all 130 | -------------------------------------------------------------------------------- /resource/sources.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Properties file for EventSource objects to be instantiated. 3 | # 4 | # Place this file in the CLASSPATH (e.g. WEB-INF/classes) or directly under WEB-INF. 5 | # 6 | # $Id: sources.properties,v 1.2 2007/11/10 14:12:16 justb Exp $ 7 | # 8 | # Each EventSource is defined as = 9 | # 1. should be unique within this file but may be any name 10 | # 2. is the full class name 11 | # 12 | # 13 | # Define Pull Sources here. These classes must be derived from 14 | # nl.justobjects.pushlet.core.EventPullSource 15 | # Inner classes are separated with a $ sign from the outer class. 16 | #source1=nl.justobjects.pushlet.test.TestEventPullSources$TemperatureEventPullSource 17 | #source2=nl.justobjects.pushlet.test.TestEventPullSources$SystemStatusEventPullSource 18 | #source3=nl.justobjects.pushlet.test.TestEventPullSources$PushletStatusEventPullSource 19 | #source4=nl.justobjects.pushlet.test.TestEventPullSources$AEXStocksEventPullSource 20 | #source5=nl.justobjects.pushlet.test.TestEventPullSources$WebPresentationEventPullSource 21 | #source6=nl.justobjects.pushlet.test.TestEventPullSources$PingEventPullSource 22 | 23 | # TO BE DONE IN NEXT VERSION 24 | # define Push Sources here. These must implement the interface 25 | # nl.justobjects.pushlet.core.EventSource 26 | 27 | -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/Version.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2000 Just Objects B.V. 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | 4 | package nl.justobjects.pushlet; 5 | 6 | /** 7 | * Version info class. 8 | * 9 | *

Purpose

10 | * Extract version info from jar manifest file. 11 | * 12 | * @author Just van den Broecke 13 | * @version $Id: Version.java,v 1.4 2006/05/06 00:10:11 justb Exp $ 14 | */ 15 | 16 | public class Version { 17 | /** Version info extracted from the .jar manifest file (see build.xml and build.properties). */ 18 | public static final String SOFTWARE_VERSION = Version.class.getPackage().getSpecificationVersion(); 19 | public static final String BUILD_DATE = Version.class.getPackage().getImplementationVersion(); 20 | } 21 | 22 | /* 23 | * $Log: Version.java,v $ 24 | * Revision 1.4 2006/05/06 00:10:11 justb 25 | * various chgs but not too serious... 26 | * 27 | * Revision 1.3 2004/02/08 16:07:55 justb 28 | * *** empty log message *** 29 | * 30 | * Revision 1.2 2003/08/15 08:37:40 justb 31 | * fix/add Copyright+LGPL file headers and footers 32 | * 33 | * Revision 1.1 2003/08/11 21:29:48 justb 34 | * first checkin 35 | * 36 | * 37 | */ 38 | -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/client/PushletClient.java.patch: -------------------------------------------------------------------------------- 1 | --- PushletClient.java 6 May 2005 20:08:20 -0000 1.16 2 | +++ PushletClient.java 28 Oct 2006 23:19:12 -0000 3 | @@ -142,7 +142,13 @@ 4 | } 5 | 6 | dataEventListener = new DataEventListener(aListener, listenURL); 7 | - dataEventListener.start(); 8 | + synchronized (dataEventListener) { 9 | + dataEventListener.start(); 10 | + try { 11 | + dataEventListener.wait(); 12 | + } catch (InterruptedException e) { 13 | + } 14 | + } 15 | } 16 | 17 | /** Immediate listener. */ 18 | @@ -354,6 +360,11 @@ 19 | // Connect to server 20 | reader = openURL(listenURL); 21 | 22 | + synchronized (this) { 23 | + // Inform the calling thread we're ready to receive events. 24 | + this.notify(); 25 | + } 26 | + 27 | // Get events while we're alive. 28 | while (receiveThread != null && receiveThread.isAlive()) { 29 | Event event = null; 30 | -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/client/PushletClientListener.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2000 Just Objects B.V. 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.Protocol; 8 | 9 | /** 10 | * Interface for listener of the PushletClient object. 11 | * 12 | * @version $Id: PushletClientListener.java,v 1.5 2005/02/21 11:50:37 justb Exp $ 13 | * @author Just van den Broecke - Just Objects © 14 | **/ 15 | public interface PushletClientListener extends Protocol { 16 | /** Abort event from server. */ 17 | public void onAbort(Event theEvent); 18 | 19 | /** Data event from server. */ 20 | public void onData(Event theEvent); 21 | 22 | /** Heartbeat event from server. */ 23 | public void onHeartbeat(Event theEvent); 24 | 25 | /** Error occurred. */ 26 | public void onError(String message); 27 | } 28 | 29 | /* 30 | * $Log: PushletClientListener.java,v $ 31 | * Revision 1.5 2005/02/21 11:50:37 justb 32 | * ohase1 of refactoring Subscriber into Session/Controller/Subscriber 33 | * 34 | * Revision 1.4 2005/02/15 15:46:31 justb 35 | * client API improves 36 | * 37 | * Revision 1.3 2004/10/24 12:58:18 justb 38 | * revised client and test classes for new protocol 39 | * 40 | * Revision 1.2 2004/09/03 22:35:37 justb 41 | * Almost complete rewrite, just checking in now 42 | * 43 | * Revision 1.1 2004/03/10 20:14:17 justb 44 | * renamed all *JavaPushletClient* to *PushletClient* 45 | * 46 | * Revision 1.3 2003/08/15 08:37:40 justb 47 | * fix/add Copyright+LGPL file headers and footers 48 | * 49 | * 50 | */ -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/core/BrowserAdapter.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2000 Just Objects B.V. 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | 4 | package nl.justobjects.pushlet.core; 5 | 6 | import nl.justobjects.pushlet.util.Log; 7 | 8 | import javax.servlet.http.HttpServletResponse; 9 | import java.io.IOException; 10 | import java.io.PrintWriter; 11 | import java.util.Iterator; 12 | 13 | /** 14 | * Generic implementation of ClientAdapter for browser clients. 15 | * 16 | * @author Just van den Broecke - Just Objects © 17 | * @version $Id: BrowserAdapter.java,v 1.6 2007/11/09 13:15:35 justb Exp $ 18 | */ 19 | public class BrowserAdapter implements ClientAdapter, Protocol { 20 | 21 | public static final String START_DOCUMENT = 22 | "" 23 | + "" 24 | + "\n"; 25 | public static final String END_DOCUMENT = ""; 26 | 27 | private PrintWriter servletOut; 28 | private HttpServletResponse servletRsp; 29 | private int bytesSent; 30 | 31 | /** 32 | * Constructor. 33 | */ 34 | public BrowserAdapter(HttpServletResponse aServletResponse) { 35 | servletRsp = aServletResponse; 36 | } 37 | 38 | /** 39 | * Generic init. 40 | */ 41 | public void start() throws IOException { 42 | // Keep servlet request/response objects until page ends in stop() 43 | // Content type as HTML 44 | servletRsp.setStatus(HttpServletResponse.SC_OK); 45 | servletRsp.setContentType("text/html;charset=UTF-8"); 46 | 47 | // http://www.junlu.com/msg/45902.html 48 | // Log.debug("bufsize=" + aRsp.getBufferSize()); 49 | servletOut = servletRsp.getWriter(); 50 | send(START_DOCUMENT); 51 | } 52 | 53 | /** 54 | * Push Event to client. 55 | */ 56 | public void push(Event anEvent) throws IOException { 57 | Log.debug("BCA event=" + anEvent.toXML()); 58 | 59 | // Check if we should refresh 60 | if (anEvent.getEventType().equals(Protocol.E_REFRESH)) { 61 | // Append refresh and tail of HTML document 62 | // Construct the JS callback line to be sent as last line of doc. 63 | // This will refresh the request using the unique id to determine 64 | // the subscriber instance on the server. The client will wait for 65 | // a number of milliseconds. 66 | long refreshWaitMillis = Long.parseLong(anEvent.getField(P_WAIT)); 67 | 68 | // Create servlet request for requesting next events (refresh) 69 | String url = anEvent.getField(P_URL); 70 | String jsRefreshTrigger = "\n"; 71 | 72 | 73 | send(jsRefreshTrigger + END_DOCUMENT); 74 | } else { 75 | send(event2JavaScript(anEvent)); 76 | } 77 | } 78 | 79 | /** 80 | * End HTML page in client browser. 81 | */ 82 | public void stop() { 83 | // To be garbage collected if adapter remains active 84 | servletOut = null; 85 | } 86 | 87 | /** 88 | * Send any string to browser. 89 | */ 90 | protected void send(String s) throws IOException { 91 | // Send string to browser. 92 | // Log.debug("Adapter: sending: " + s); 93 | if (servletOut == null) { 94 | throw new IOException("Client adapter was stopped"); 95 | } 96 | 97 | servletOut.print(s); 98 | 99 | servletOut.flush(); 100 | 101 | // Note: this doesn't seem to have effect 102 | // in Tomcat 4/5 if the client already disconnected. 103 | servletRsp.flushBuffer(); 104 | 105 | bytesSent += s.length(); 106 | Log.debug("bytesSent= " + bytesSent); 107 | // Log.debug("BCA sent event: " + s); 108 | } 109 | 110 | /** 111 | * Converts the Java Event to a JavaScript function call in browser page. 112 | */ 113 | protected String event2JavaScript(Event event) throws IOException { 114 | 115 | // Convert the event to a comma-separated string. 116 | String jsArgs = ""; 117 | for (Iterator iter = event.getFieldNames(); iter.hasNext();) { 118 | String name = (String) iter.next(); 119 | String value = event.getField(name); 120 | String nextArgument = (jsArgs.equals("") ? "" : ",") + "'" + name + "'" + ", \"" + value + "\""; 121 | jsArgs += nextArgument; 122 | } 123 | 124 | // Construct and return the function call */ 125 | return ""; 126 | } 127 | 128 | } 129 | 130 | /* 131 | * $Log: BrowserAdapter.java,v $ 132 | * Revision 1.6 2007/11/09 13:15:35 justb 133 | * add charset=UTF-8 in returned HTTP content types 134 | * 135 | * Revision 1.5 2006/05/15 11:52:53 justb 136 | * updates mainly for AJAX client 137 | * 138 | * Revision 1.4 2006/05/06 00:10:11 justb 139 | * various chgs but not too serious... 140 | * 141 | * Revision 1.3 2005/02/28 12:45:59 justb 142 | * introduced Command class 143 | * 144 | * Revision 1.2 2005/02/21 11:50:44 justb 145 | * ohase1 of refactoring Subscriber into Session/Controller/Subscriber 146 | * 147 | * Revision 1.1 2005/02/18 10:07:23 justb 148 | * many renamings of classes (make names compact) 149 | * 150 | * Revision 1.12 2005/02/15 13:30:23 justb 151 | * changes for Tomcat buffering (now working in tc4 and 5.0) 152 | * 153 | * Revision 1.11 2005/01/24 22:45:58 justb 154 | * getting safari to work 155 | * 156 | * Revision 1.10 2005/01/18 16:46:27 justb 157 | * buffer size setting ignored by tomcat workings 158 | * 159 | * Revision 1.9 2004/10/24 12:58:18 justb 160 | * revised client and test classes for new protocol 161 | * 162 | * Revision 1.8 2004/09/20 22:01:38 justb 163 | * more changes for new protocol 164 | * 165 | * Revision 1.7 2004/09/03 22:35:37 justb 166 | * Almost complete rewrite, just checking in now 167 | * 168 | * Revision 1.6 2004/08/15 16:00:15 justb 169 | * enhancements to pull mode 170 | * 171 | * Revision 1.5 2004/08/13 23:36:05 justb 172 | * rewrite of Pullet into Pushlet "pull" mode 173 | * 174 | * Revision 1.4 2003/08/15 08:37:40 justb 175 | * fix/add Copyright+LGPL file headers and footers 176 | * 177 | * Revision 1.3 2003/08/12 09:57:05 justb 178 | * replaced all print statements to Log.*() calls 179 | * 180 | * Revision 1.2 2003/05/18 16:15:07 justb 181 | * support for XML encoded Events 182 | * 183 | * Revision 1.1.1.1 2002/09/24 21:02:30 justb 184 | * import to sourceforge 185 | * 186 | * Revision 1.1.1.1 2002/09/20 22:48:17 justb 187 | * import to SF 188 | * 189 | * Revision 1.1.1.1 2002/09/20 14:19:02 justb 190 | * first import into SF 191 | * 192 | * Revision 1.5 2002/04/15 20:42:41 just 193 | * reformatting and renaming GuardedQueue to EventQueue 194 | * 195 | * Revision 1.4 2000/12/27 22:39:35 just 196 | * no message 197 | * 198 | * Revision 1.3 2000/10/30 14:15:47 just 199 | * no message 200 | * 201 | * 202 | */ 203 | 204 | -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/core/ClientAdapter.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2000 Just Objects B.V. 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | 4 | package nl.justobjects.pushlet.core; 5 | 6 | import java.io.IOException; 7 | 8 | /** 9 | * Adapter interface for encapsulation of specific HTTP clients. 10 | * 11 | * @author Just van den Broecke - Just Objects © 12 | * @version $Id: ClientAdapter.java,v 1.8 2007/11/23 14:33:07 justb Exp $ 13 | */ 14 | public interface ClientAdapter { 15 | 16 | /** 17 | * Start event push. 18 | */ 19 | public void start() throws IOException; 20 | 21 | /** 22 | * Push single Event to client. 23 | */ 24 | public void push(Event anEvent) throws IOException; 25 | 26 | /** 27 | * Stop event push. 28 | */ 29 | public void stop() throws IOException; 30 | } 31 | 32 | /* 33 | * $Log: ClientAdapter.java,v $ 34 | * Revision 1.8 2007/11/23 14:33:07 justb 35 | * core classes now configurable through factory 36 | * 37 | * Revision 1.7 2005/02/28 12:45:59 justb 38 | * introduced Command class 39 | * 40 | * Revision 1.6 2005/02/21 11:50:45 justb 41 | * ohase1 of refactoring Subscriber into Session/Controller/Subscriber 42 | * 43 | * Revision 1.5 2005/02/18 10:07:23 justb 44 | * many renamings of classes (make names compact) 45 | * 46 | * Revision 1.4 2004/09/03 22:35:37 justb 47 | * Almost complete rewrite, just checking in now 48 | * 49 | * Revision 1.3 2003/08/15 08:37:40 justb 50 | * fix/add Copyright+LGPL file headers and footers 51 | * 52 | * Revision 1.2 2003/05/18 16:15:08 justb 53 | * support for XML encoded Events 54 | * 55 | * Revision 1.1.1.1 2002/09/24 21:02:30 justb 56 | * import to sourceforge 57 | * 58 | * Revision 1.1.1.1 2002/09/20 22:48:17 justb 59 | * import to SF 60 | * 61 | * Revision 1.1.1.1 2002/09/20 14:19:03 justb 62 | * first import into SF 63 | * 64 | * Revision 1.3 2002/04/15 20:42:41 just 65 | * reformatting and renaming GuardedQueue to EventQueue 66 | * 67 | * Revision 1.2 2000/08/21 20:48:29 just 68 | * added CVS log and id tags plus copyrights 69 | * 70 | * 71 | */ 72 | 73 | -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/core/Command.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2000 Just Objects B.V. 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | 4 | package nl.justobjects.pushlet.core; 5 | 6 | import nl.justobjects.pushlet.util.PushletException; 7 | import nl.justobjects.pushlet.util.Servlets; 8 | 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | 12 | /** 13 | * Wraps pushlet request/response data. 14 | * 15 | * @author Just van den Broecke - Just Objects © 16 | * @version $Id: Command.java,v 1.4 2007/11/23 14:33:07 justb Exp $ 17 | */ 18 | public class Command implements Protocol { 19 | 20 | /** 21 | * Pushlet request event. 22 | */ 23 | public final Event reqEvent; 24 | 25 | /** 26 | * Pushlet response event. 27 | */ 28 | private Event rspEvent; 29 | 30 | /** 31 | * HTTP Servlet GET/POST request. 32 | */ 33 | public final HttpServletRequest httpReq; 34 | 35 | /** 36 | * HTTP Servlet GET/POST response. 37 | */ 38 | public final HttpServletResponse httpRsp; 39 | 40 | /** 41 | * Pushlet session. 42 | */ 43 | public final Session session; 44 | 45 | /** 46 | * Per-response client adapter. 47 | */ 48 | private ClientAdapter clientAdapter; 49 | 50 | /** 51 | * Constructor. 52 | */ 53 | private Command(Session aSession, Event aRequestEvent, HttpServletRequest aHTTPReq, HttpServletResponse aHTTPRsp) { 54 | session = aSession; 55 | reqEvent = aRequestEvent; 56 | httpReq = aHTTPReq; 57 | httpRsp = aHTTPRsp; 58 | } 59 | 60 | /** 61 | * Create new Command object. 62 | */ 63 | public static Command create(Session aSession, Event aReqEvent, HttpServletRequest aHTTPReq, HttpServletResponse aHTTPRsp) { 64 | return new Command(aSession, aReqEvent, aHTTPReq, aHTTPRsp); 65 | } 66 | 67 | /** 68 | * Set pushlet response event. 69 | */ 70 | public void setResponseEvent(Event aResponseEvent) { 71 | rspEvent = aResponseEvent; 72 | } 73 | 74 | /** 75 | * Get pushlet response event. 76 | */ 77 | public Event getResponseEvent() { 78 | return rspEvent; 79 | } 80 | 81 | /** 82 | * Get client adapter for request. 83 | */ 84 | public ClientAdapter getClientAdapter() throws PushletException { 85 | if (clientAdapter == null) { 86 | // Create and initialize client-specific adapter. 87 | clientAdapter = createClientAdapter(); 88 | } 89 | return clientAdapter; 90 | } 91 | 92 | /** 93 | * Create client notifier based on "format" parameter passed in request. 94 | */ 95 | protected ClientAdapter createClientAdapter() throws PushletException { 96 | 97 | // Assumed to be set by parent. 98 | String outputFormat = session.getFormat(); 99 | 100 | // Determine client adapter to create. 101 | if (outputFormat.equals(FORMAT_JAVASCRIPT)) { 102 | // Client expects to receive Events as JavaScript dispatch calls.. 103 | return new BrowserAdapter(httpRsp); 104 | } else if (outputFormat.equals(FORMAT_SERIALIZED_JAVA_OBJECT)) { 105 | // Client expects to receive Events as Serialized Java Objects. 106 | return new SerializedAdapter(httpRsp); 107 | } else if (outputFormat.equals(FORMAT_XML)) { 108 | // Client expects to receive Events as stream of XML docs. 109 | return new XMLAdapter(httpRsp); 110 | } else if (outputFormat.equals(FORMAT_XML_STRICT)) { 111 | // Client expects to receive Events embedded in single XML doc. 112 | return new XMLAdapter(httpRsp, true); 113 | } else { 114 | throw new PushletException("Null or invalid output format: " + outputFormat); 115 | } 116 | } 117 | 118 | /** 119 | * Sends HTTP response headers. 120 | */ 121 | protected void sendResponseHeaders() { 122 | // Just to try to prevent caching in any form. 123 | Servlets.setNoCacheHeaders(httpRsp); 124 | 125 | // Close connection for Java enabled browsers 126 | if (session.getUserAgent().indexOf("java") > 0) { 127 | // The connection should be closed after this request 128 | // NB: this allows sending a "long response". Some clients 129 | // in particular java.net.URL in VMs > 1.1 that use HTTP/1.1 130 | // will block if 131 | // - the content length is not sent 132 | // - if Connection: close HTTP header is not sent. 133 | // 134 | // Since we don't know the content length we will assume 135 | // the underlying servlet engine will use chunked encoding. 136 | httpRsp.setHeader("Connection", "close"); 137 | } 138 | } 139 | 140 | 141 | } 142 | 143 | /* 144 | * $Log: Command.java,v $ 145 | * Revision 1.4 2007/11/23 14:33:07 justb 146 | * core classes now configurable through factory 147 | * 148 | * Revision 1.3 2005/05/06 19:44:00 justb 149 | * added xml-strict format 150 | * 151 | * Revision 1.2 2005/02/28 17:25:15 justb 152 | * commented 153 | * 154 | * Revision 1.1 2005/02/28 12:45:59 justb 155 | * introduced Command class 156 | * 157 | * 158 | */ 159 | -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/core/Config.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2000 Just Objects B.V. 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | 4 | package nl.justobjects.pushlet.core; 5 | 6 | import nl.justobjects.pushlet.util.Log; 7 | import nl.justobjects.pushlet.util.PushletException; 8 | import nl.justobjects.pushlet.util.Sys; 9 | 10 | import java.io.File; 11 | import java.util.Properties; 12 | 13 | /** 14 | * Loads and maintains overall configuration. 15 | * 16 | * @author Just van den Broecke - Just Objects © 17 | * @version $Id: Config.java,v 1.5 2007/11/23 21:10:17 justb Exp $ 18 | */ 19 | public class Config implements ConfigDefs { 20 | private static final String PROPERTIES_FILE = "pushlet.properties"; 21 | private static Properties properties; 22 | 23 | /** 24 | * Factory method: create object from property denoting class name. 25 | * 26 | * @param aClassNameProp property name e.g. "session.class" 27 | * @return an instance of class denoted by property 28 | * @throws PushletException when class cannot be instantiated 29 | */ 30 | public static Object createObject(String aClassNameProp, String aDefault) throws PushletException { 31 | Class clazz = getClass(aClassNameProp, aDefault); 32 | try { 33 | return clazz.newInstance(); 34 | } catch (Throwable t) { 35 | // Usually a misconfiguration 36 | throw new PushletException("Cannot instantiate class for " + aClassNameProp + "=" + clazz, t); 37 | } 38 | } 39 | 40 | /** 41 | * Factory method: create object from property denoting class name. 42 | * 43 | * @param aClassNameProp property name e.g. "session.class" 44 | * @return a Class object denoted by property 45 | * @throws PushletException when class cannot be instantiated 46 | */ 47 | public static Class getClass(String aClassNameProp, String aDefault) throws PushletException { 48 | // Singleton + factory pattern: create object instance 49 | // from configured class name 50 | String clazz = (aDefault == null ? getProperty(aClassNameProp) : getProperty(aClassNameProp, aDefault)); 51 | 52 | try { 53 | return Class.forName(clazz); 54 | } catch (ClassNotFoundException t) { 55 | // Usually a misconfiguration 56 | throw new PushletException("Cannot find class for " + aClassNameProp + "=" + clazz, t); 57 | } 58 | } 59 | 60 | /** 61 | * Initialize event sources from properties file. 62 | */ 63 | public static void load(String aDirPath) { 64 | // Load Event sources using properties file. 65 | try { 66 | // Try loading through classpath first (e.g. in WEB-INF/classes or from .jar) 67 | Log.info("Config: loading " + PROPERTIES_FILE + " from classpath"); 68 | properties = Sys.loadPropertiesResource(PROPERTIES_FILE); 69 | } catch (Throwable t) { 70 | // Try from provided dir (e.g. WEB_INF/pushlet.properties) 71 | String filePath = aDirPath + File.separator + PROPERTIES_FILE; 72 | Log.info("Config: cannot load " + PROPERTIES_FILE + " from classpath, will try from " + filePath); 73 | 74 | try { 75 | properties = Sys.loadPropertiesFile(filePath); 76 | } catch (Throwable t2) { 77 | Log.fatal("Config: cannot load properties file from " + filePath, t); 78 | 79 | // Give up 80 | return; 81 | } 82 | } 83 | 84 | Log.info("Config: loaded values=" + properties); 85 | } 86 | 87 | public static String getProperty(String aName, String aDefault) { 88 | return properties.getProperty(aName, aDefault); 89 | } 90 | 91 | public static String getProperty(String aName) { 92 | String value = properties.getProperty(aName); 93 | if (value == null) { 94 | throw new IllegalArgumentException("Unknown property: " + aName); 95 | } 96 | return value; 97 | } 98 | 99 | public static boolean getBoolProperty(String aName) { 100 | String value = getProperty(aName); 101 | try { 102 | return value.equals("true"); 103 | } catch (Throwable t) { 104 | throw new IllegalArgumentException("Illegal property value: " + aName + " val=" + value); 105 | } 106 | } 107 | 108 | public static int getIntProperty(String aName) { 109 | String value = getProperty(aName); 110 | try { 111 | return Integer.parseInt(value); 112 | } catch (Throwable t) { 113 | throw new IllegalArgumentException("Illegal property value: " + aName + " val=" + value); 114 | } 115 | } 116 | 117 | public static long getLongProperty(String aName) { 118 | String value = getProperty(aName); 119 | try { 120 | return Long.parseLong(value); 121 | } catch (Throwable t) { 122 | throw new IllegalArgumentException("Illegal property value: " + aName + " val=" + value); 123 | } 124 | } 125 | 126 | public static boolean hasProperty(String aName) { 127 | return properties.containsKey(aName); 128 | } 129 | 130 | 131 | } 132 | 133 | /* 134 | * $Log: Config.java,v $ 135 | * Revision 1.5 2007/11/23 21:10:17 justb 136 | * add hooks for custom logging (you can override DefaultLogger in pushlet.properties) 137 | * 138 | * Revision 1.4 2007/11/23 14:33:07 justb 139 | * core classes now configurable through factory 140 | * 141 | * Revision 1.3 2007/11/10 13:44:02 justb 142 | * pushlet.properties and sources.properties can now also be put under WEB-INF 143 | * 144 | * Revision 1.2 2006/05/06 00:10:11 justb 145 | * various chgs but not too serious... 146 | * 147 | * Revision 1.1 2005/02/18 12:36:47 justb 148 | * changes for renaming and configurability 149 | * 150 | 151 | * 152 | */ 153 | -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/core/ConfigDefs.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2000 Just Objects B.V. 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | 4 | package nl.justobjects.pushlet.core; 5 | 6 | 7 | /** 8 | * Definition of config property strings. 9 | * 10 | * @author Just van den Broecke - Just Objects © 11 | * @version $Id: ConfigDefs.java,v 1.9 2007/12/07 12:57:40 justb Exp $ 12 | */ 13 | public interface ConfigDefs { 14 | /** 15 | * Class factory definitions, used to insert your custom classes. 16 | */ 17 | public static final String CONTROLLER_CLASS = "controller.class"; 18 | public static final String DISPATCHER_CLASS = "dispatcher.class"; 19 | public static final String LOGGER_CLASS = "logger.class"; 20 | public static final String SESSION_MANAGER_CLASS = "sessionmanager.class"; 21 | public static final String SESSION_CLASS = "session.class"; 22 | public static final String SUBSCRIBER_CLASS = "subscriber.class"; 23 | public static final String SUBSCRIPTION_CLASS = "subscription.class"; 24 | 25 | /** 26 | * Session management. 27 | */ 28 | public static final String SESSION_ID_SIZE = "session.id.size"; 29 | public static final String SESSION_ID_GENERATION = "session.id.generation"; 30 | public static final String SESSION_ID_GENERATION_UUID = "uuid"; 31 | public static final String SESSION_ID_GENERATION_RANDOMSTRING = "randomstring"; 32 | public static final String SESSION_TIMEOUT_MINS = "session.timeout.mins"; 33 | 34 | public static final String SOURCES_ACTIVATE = "sources.activate"; 35 | 36 | /** 37 | * Logging 38 | */ 39 | public static final String LOG_LEVEL = "log.level"; 40 | public static final int LOG_LEVEL_FATAL = 1; 41 | public static final int LOG_LEVEL_ERROR = 2; 42 | public static final int LOG_LEVEL_WARN = 3; 43 | public static final int LOG_LEVEL_INFO = 4; 44 | public static final int LOG_LEVEL_DEBUG = 5; 45 | public static final int LOG_LEVEL_TRACE = 6; 46 | 47 | /** 48 | * Queues 49 | */ 50 | public static final String QUEUE_SIZE = "queue.size"; 51 | public static final String QUEUE_READ_TIMEOUT_MILLIS = "queue.read.timeout.millis"; 52 | public static final String QUEUE_WRITE_TIMEOUT_MILLIS = "queue.write.timeout.millis"; 53 | 54 | /** 55 | * Listening modes. 56 | */ 57 | public static final String LISTEN_FORCE_PULL_ALL = "listen.force.pull.all"; 58 | public static final String LISTEN_FORCE_PULL_AGENTS = "listen.force.pull.agents"; 59 | 60 | 61 | public static final String PULL_REFRESH_TIMEOUT_MILLIS = "pull.refresh.timeout.millis"; 62 | public static final String PULL_REFRESH_WAIT_MIN_MILLIS = "pull.refresh.wait.min.millis"; 63 | public static final String PULL_REFRESH_WAIT_MAX_MILLIS = "pull.refresh.wait.max.millis"; 64 | 65 | 66 | public static final String POLL_REFRESH_TIMEOUT_MILLIS = "poll.refresh.timeout.millis"; 67 | public static final String POLL_REFRESH_WAIT_MIN_MILLIS = "poll.refresh.wait.min.millis"; 68 | public static final String POLL_REFRESH_WAIT_MAX_MILLIS = "poll.refresh.wait.max.millis"; 69 | 70 | } 71 | 72 | /* 73 | * $Log: ConfigDefs.java,v $ 74 | * Revision 1.9 2007/12/07 12:57:40 justb 75 | * added log4j and make it the default logging method 76 | * 77 | * Revision 1.8 2007/11/23 21:10:17 justb 78 | * add hooks for custom logging (you can override DefaultLogger in pushlet.properties) 79 | * 80 | * Revision 1.7 2007/11/23 14:33:07 justb 81 | * core classes now configurable through factory 82 | * 83 | * Revision 1.6 2007/11/10 14:48:35 justb 84 | * make session key generation configurable (can use uuid) 85 | * 86 | * Revision 1.5 2005/02/28 09:14:55 justb 87 | * sessmgr/dispatcher factory/singleton support 88 | * 89 | * Revision 1.4 2005/02/21 16:59:00 justb 90 | * SessionManager and session lease introduced 91 | * 92 | * Revision 1.3 2005/02/21 11:50:46 justb 93 | * ohase1 of refactoring Subscriber into Session/Controller/Subscriber 94 | * 95 | * Revision 1.2 2005/02/21 11:16:44 justb 96 | * add log level config prop 97 | * 98 | * Revision 1.1 2005/02/18 12:36:47 justb 99 | * changes for renaming and configurability 100 | * 101 | * 102 | * 103 | */ 104 | 105 | -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/core/Dispatcher.java: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushelp/Pushlet-UserUnicast/b423f8e1cde4ae761d16cf55d732a16d5d540d1a/src/nl/justobjects/pushlet/core/Dispatcher.java -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/core/Event.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2000 Just Objects B.V. 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | 4 | package nl.justobjects.pushlet.core; 5 | 6 | import nl.justobjects.pushlet.util.Sys; 7 | 8 | import java.io.Serializable; 9 | import java.util.HashMap; 10 | import java.util.Iterator; 11 | import java.util.Map; 12 | 13 | /** 14 | * Represents the event data. 15 | * 16 | * @author Just van den Broecke - Just Objects © 17 | * @version $Id: Event.java,v 1.13 2007/11/23 14:33:07 justb Exp $ 18 | */ 19 | public class Event implements Protocol, Serializable { 20 | 21 | protected Map attributes = new HashMap(3); 22 | 23 | public Event(String anEventType) { 24 | this(anEventType, null); 25 | } 26 | 27 | public Event(String anEventType, Map theAttributes) { 28 | 29 | if (theAttributes != null) { 30 | setAttrs(theAttributes); 31 | } 32 | 33 | // Set required field event type 34 | setField(P_EVENT, anEventType); 35 | 36 | // Set time in seconds since 1970 37 | setField(P_TIME, System.currentTimeMillis() / 1000); 38 | } 39 | 40 | public Event(Map theAttributes) { 41 | if (!theAttributes.containsKey(P_EVENT)) { 42 | throw new IllegalArgumentException(P_EVENT + " not found in attributes"); 43 | } 44 | setAttrs(theAttributes); 45 | } 46 | 47 | public static Event createDataEvent(String aSubject) { 48 | return createDataEvent(aSubject, null); 49 | } 50 | 51 | public static Event createDataEvent(String aSubject, Map theAttributes) { 52 | Event dataEvent = new Event(E_DATA, theAttributes); 53 | dataEvent.setField(P_SUBJECT, aSubject); 54 | return dataEvent; 55 | } 56 | 57 | public String getEventType() { 58 | return getField(P_EVENT); 59 | } 60 | 61 | public String getSubject() { 62 | return getField(P_SUBJECT); 63 | } 64 | 65 | public void setField(String name, String value) { 66 | attributes.put(name, value); 67 | } 68 | 69 | public void setField(String name, int value) { 70 | attributes.put(name, value + ""); 71 | } 72 | 73 | public void setField(String name, long value) { 74 | attributes.put(name, value + ""); 75 | } 76 | 77 | public String getField(String name) { 78 | return (String) attributes.get(name); 79 | } 80 | 81 | /** 82 | * Return field; if null return default. 83 | */ 84 | public String getField(String name, String aDefault) { 85 | String result = getField(name); 86 | return result == null ? aDefault : result; 87 | } 88 | 89 | public Iterator getFieldNames() { 90 | return attributes.keySet().iterator(); 91 | } 92 | 93 | public String toString() { 94 | return attributes.toString(); 95 | } 96 | 97 | /** 98 | * Convert to HTTP query string. 99 | */ 100 | public String toQueryString() { 101 | String queryString = ""; 102 | String amp = ""; 103 | for (Iterator iter = getFieldNames(); iter.hasNext();) { 104 | String nextAttrName = (String) iter.next(); 105 | String nextAttrValue = getField(nextAttrName); 106 | queryString = queryString + amp + nextAttrName + "=" + nextAttrValue; 107 | // After first add "&". 108 | amp = "&"; 109 | } 110 | 111 | return queryString; 112 | } 113 | 114 | public String toXML(boolean strict) { 115 | String xmlString = " 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | 4 | package nl.justobjects.pushlet.core; 5 | 6 | import java.io.*; 7 | import java.util.HashMap; 8 | 9 | /** 10 | * Parses XML into Event objects. 11 | * 12 | * @author Just van den Broecke - Just Objects © 13 | * @version $Id: EventParser.java,v 1.3 2007/11/23 14:33:07 justb Exp $ 14 | */ 15 | public class EventParser { 16 | 17 | 18 | private EventParser() { 19 | } 20 | 21 | /** 22 | * Parse Event from a File. 23 | */ 24 | public static Event parse(File aFile) throws IOException { 25 | BufferedReader br = new BufferedReader(new FileReader(aFile)); 26 | return parse(br); 27 | } 28 | 29 | /** 30 | * Parse Event from input Reader. 31 | */ 32 | public static Event parse(Reader aReader) throws IOException { 33 | StringBuffer preparsedString = new StringBuffer(24); 34 | 35 | // First find the opening tag ('<') 36 | char nextChar; 37 | while ((nextChar = (char) aReader.read()) != '<') ; 38 | 39 | // Append '<' 40 | preparsedString.append(nextChar); 41 | 42 | // Then find end-tag ('>'), appending all chars to preparsed string. 43 | do { 44 | nextChar = (char) aReader.read(); 45 | preparsedString.append(nextChar); 46 | } while (nextChar != '>'); 47 | 48 | return parse(preparsedString.toString()); 49 | } 50 | 51 | /** 52 | * Parse Event from a String. 53 | */ 54 | public static Event parse(String aString) throws IOException { 55 | aString = aString.trim(); 56 | 57 | if (!aString.startsWith("<") || !aString.endsWith("/>")) { 58 | throw new IOException("No start or end tag found while parsing event [" + aString + "]"); 59 | } 60 | 61 | // Create the attributes object. 62 | HashMap properties = new HashMap(3); 63 | 64 | // Remove the start and end (< ... />) from the string 65 | aString = aString.substring(1, aString.length() - 2).trim(); 66 | 67 | int index = 0; 68 | 69 | // Parse the tag 70 | while (!Character.isWhitespace(aString.charAt(index)) 71 | && (index < aString.length())) { 72 | index++; 73 | } 74 | 75 | // We don't use the tag: remove from string 76 | aString = aString.substring(index).trim(); 77 | index = 0; 78 | 79 | String attrName; 80 | String attrValue; 81 | 82 | while (index < aString.length()) { 83 | 84 | // Parse attribute name 85 | while ((aString.charAt(index) != '=') 86 | && (index < aString.length())) { 87 | index++; 88 | } 89 | 90 | // Create attr name string 91 | attrName = aString.substring(0, index).trim(); 92 | 93 | // remove the attributeName and the '=' from the string 94 | aString = aString.substring(index + 1).trim(); 95 | index = 1; // read past the first wrapping "\"" 96 | 97 | // Parse attribute value 98 | while ((aString.charAt(index) != '\"') 99 | && (index < aString.length())) { 100 | 101 | // bypass the special characters '\' and '"' inside the 102 | // attributevalue itself which are deliniated with a preceding 103 | // '\' 104 | if (aString.charAt(index) == '\\') { 105 | aString = aString.substring(0, index) 106 | + aString.substring(index + 1); // remove the '\' 107 | } 108 | 109 | index++; 110 | } 111 | 112 | // create the attribute value; exclude the wrapping quote-characters 113 | attrValue = aString.substring(1, index); 114 | 115 | // Set the attribute N/V 116 | properties.put(attrName, attrValue); 117 | 118 | aString = aString.substring(index + 1).trim(); 119 | index = 0; 120 | } 121 | 122 | return new Event(properties); 123 | } 124 | 125 | /** 126 | * Test method: use files to test. 127 | */ 128 | public static void main(String[] args) { 129 | try { 130 | Event event = parse(new File(args[0])); 131 | System.out.println("OK parsed Event file " + args[0]); 132 | System.out.println(event.toXML()); 133 | 134 | event = parse(event.toXML()); 135 | System.out.println("OK parsed Event string"); 136 | System.out.println(event.toXML()); 137 | } catch (Throwable t) { 138 | System.out.println("Error parsing event file: " + args[0]); 139 | t.printStackTrace(); 140 | } 141 | } 142 | } 143 | 144 | /* 145 | * $Log: EventParser.java,v $ 146 | * Revision 1.3 2007/11/23 14:33:07 justb 147 | * core classes now configurable through factory 148 | * 149 | * Revision 1.2 2006/05/06 00:10:11 justb 150 | * various chgs but not too serious... 151 | * 152 | * Revision 1.1 2005/02/18 10:07:23 justb 153 | * many renamings of classes (make names compact) 154 | * 155 | * Revision 1.3 2004/09/03 22:35:37 justb 156 | * Almost complete rewrite, just checking in now 157 | * 158 | * Revision 1.2 2003/08/15 08:37:40 justb 159 | * fix/add Copyright+LGPL file headers and footers 160 | * 161 | * Revision 1.1 2003/05/18 16:12:27 justb 162 | * adding support for XML encoded Events 163 | * 164 | */ 165 | -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/core/EventPullSource.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2000 Just Objects B.V. 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | 4 | package nl.justobjects.pushlet.core; 5 | 6 | import nl.justobjects.pushlet.util.Log; 7 | 8 | /** 9 | * Abstract Event source from which Events are pulled. 10 | * 11 | * @version $Id: EventPullSource.java,v 1.15 2007/11/23 14:33:07 justb Exp $ 12 | * @author Just van den Broecke - Just Objects © 13 | **/ 14 | 15 | /** 16 | * ABC for specifc EventPullSources. 17 | */ 18 | abstract public class EventPullSource implements EventSource, Runnable { 19 | private volatile boolean alive = false; 20 | private volatile boolean active = false; 21 | private static int threadNum = 0; 22 | private Thread thread; 23 | 24 | public EventPullSource() { 25 | } 26 | 27 | abstract protected long getSleepTime(); 28 | 29 | abstract protected Event pullEvent(); 30 | 31 | public void start() { 32 | thread = new Thread(this, "EventPullSource-" + (++threadNum)); 33 | thread.setDaemon(true); 34 | thread.start(); 35 | } 36 | 37 | public boolean isAlive() { 38 | return alive; 39 | } 40 | 41 | /** 42 | * Stop the event generator thread. 43 | */ 44 | public void stop() { 45 | alive = false; 46 | 47 | if (thread != null) { 48 | thread.interrupt(); 49 | thread = null; 50 | } 51 | 52 | } 53 | 54 | /** 55 | * Activate the event generator thread. 56 | */ 57 | synchronized public void activate() { 58 | if (active) { 59 | return; 60 | } 61 | active = true; 62 | if (!alive) { 63 | start(); 64 | return; 65 | } 66 | Log.debug(getClass().getName() + ": notifying..."); 67 | notifyAll(); 68 | } 69 | 70 | /** 71 | * Deactivate the event generator thread. 72 | */ 73 | public void passivate() { 74 | if (!active) { 75 | return; 76 | } 77 | active = false; 78 | } 79 | 80 | /** 81 | * Main loop: sleep, generate event and publish. 82 | */ 83 | public void run() { 84 | Log.debug(getClass().getName() + ": starting..."); 85 | alive = true; 86 | while (alive) { 87 | try { 88 | 89 | Thread.sleep(getSleepTime()); 90 | 91 | // Stopped during sleep: end loop. 92 | if (!alive) { 93 | break; 94 | } 95 | 96 | // If passivated wait until we get 97 | // get notify()-ied. If there are no subscribers 98 | // it wasts CPU to remain producing events... 99 | synchronized (this) { 100 | while (!active) { 101 | Log.debug(getClass().getName() + ": waiting..."); 102 | wait(); 103 | } 104 | } 105 | 106 | } catch (InterruptedException e) { 107 | break; 108 | } 109 | 110 | try { 111 | // Derived class should produce an event. 112 | Event event = pullEvent(); 113 | 114 | // Let the publisher push it to subscribers. 115 | Dispatcher.getInstance().multicast(event); 116 | } catch (Throwable t) { 117 | Log.warn("EventPullSource exception while multicasting ", t); 118 | t.printStackTrace(); 119 | } 120 | } 121 | Log.debug(getClass().getName() + ": stopped"); 122 | } 123 | } 124 | 125 | /* 126 | * $Log: EventPullSource.java,v $ 127 | * Revision 1.15 2007/11/23 14:33:07 justb 128 | * core classes now configurable through factory 129 | * 130 | * Revision 1.14 2005/02/28 09:14:55 justb 131 | * sessmgr/dispatcher factory/singleton support 132 | * 133 | * Revision 1.13 2005/02/21 16:59:08 justb 134 | * SessionManager and session lease introduced 135 | * 136 | * Revision 1.12 2005/02/21 11:50:46 justb 137 | * ohase1 of refactoring Subscriber into Session/Controller/Subscriber 138 | * 139 | * Revision 1.11 2005/02/18 10:07:23 justb 140 | * many renamings of classes (make names compact) 141 | * 142 | * Revision 1.10 2005/02/18 09:54:15 justb 143 | * refactor: rename Publisher Dispatcher and single Subscriber class 144 | * 145 | * Revision 1.9 2004/09/20 22:01:38 justb 146 | * more changes for new protocol 147 | * 148 | * Revision 1.8 2004/09/03 22:35:37 justb 149 | * Almost complete rewrite, just checking in now 150 | * 151 | * Revision 1.7 2004/08/15 16:00:15 justb 152 | * enhancements to pull mode 153 | * 154 | * Revision 1.6 2004/08/13 23:36:05 justb 155 | * rewrite of Pullet into Pushlet "pull" mode 156 | * 157 | * Revision 1.5 2004/03/10 14:01:55 justb 158 | * formatting and *Subscriber refactoring 159 | * 160 | * Revision 1.4 2003/08/15 08:37:40 justb 161 | * fix/add Copyright+LGPL file headers and footers 162 | * 163 | * Revision 1.3 2003/08/12 09:57:05 justb 164 | * replaced all print statements to Log.*() calls 165 | * 166 | * Revision 1.2 2003/05/18 16:15:08 justb 167 | * support for XML encoded Events 168 | * 169 | * Revision 1.1.1.1 2002/09/24 21:02:31 justb 170 | * import to sourceforge 171 | * 172 | * Revision 1.1.1.1 2002/09/20 22:48:17 justb 173 | * import to SF 174 | * 175 | * Revision 1.1.1.1 2002/09/20 14:19:03 justb 176 | * first import into SF 177 | * 178 | * Revision 1.3 2002/04/15 20:42:41 just 179 | * reformatting and renaming GuardedQueue to EventQueue 180 | * 181 | * Revision 1.2 2000/08/21 20:48:29 just 182 | * added CVS log and id tags plus copyrights 183 | * 184 | * 185 | */ 186 | -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/core/EventQueue.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2000 Just Objects B.V. 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | 4 | package nl.justobjects.pushlet.core; 5 | 6 | /** 7 | * FIFO queue with guarded suspension. 8 | * Purpose
9 | *

10 | * Implementation
11 | * FIFO queue class implemented with circular array. The enQueue() and 12 | * deQueue() methods use guarded suspension according to a readers/writers 13 | * pattern, implemented with java.lang.Object.wait()/notify(). 14 | *

15 | * Examples
16 | *

17 | *
18 | * 19 | * @author Just van den Broecke - Just Objects © 20 | * @version $Id: EventQueue.java,v 1.3 2007/11/23 14:33:07 justb Exp $ 21 | */ 22 | public class EventQueue { 23 | /** 24 | * Defines maximum queue size 25 | */ 26 | private int capacity = 8; 27 | private Event[] queue = null; 28 | private int front, rear; 29 | 30 | /** 31 | * Construct queue with default (8) capacity. 32 | */ 33 | public EventQueue() { 34 | this(8); 35 | } 36 | 37 | /** 38 | * Construct queue with specified capacity. 39 | */ 40 | public EventQueue(int capacity) { 41 | this.capacity = capacity; 42 | queue = new Event[capacity]; 43 | front = rear = 0; 44 | } 45 | 46 | /** 47 | * Put item in queue; waits() indefinitely if queue is full. 48 | */ 49 | public synchronized boolean enQueue(Event item) throws InterruptedException { 50 | return enQueue(item, -1); 51 | } 52 | 53 | /** 54 | * Put item in queue; if full wait maxtime. 55 | */ 56 | public synchronized boolean enQueue(Event item, long maxWaitTime) throws InterruptedException { 57 | 58 | // Wait (optional maxtime) as long as the queue is full 59 | while (isFull()) { 60 | if (maxWaitTime > 0) { 61 | // Wait at most maximum time 62 | wait(maxWaitTime); 63 | 64 | // Timed out or woken; if still full we 65 | // had bad luck and return failure. 66 | if (isFull()) { 67 | return false; 68 | } 69 | } else { 70 | wait(); 71 | } 72 | } 73 | 74 | // Put item in queue 75 | queue[rear] = item; 76 | rear = next(rear); 77 | 78 | // Wake up waiters; NOTE: first waiter will eat item 79 | notifyAll(); 80 | return true; 81 | } 82 | 83 | /** 84 | * Get head; if empty wait until something in queue. 85 | */ 86 | public synchronized Event deQueue() throws InterruptedException { 87 | return deQueue(-1); 88 | } 89 | 90 | /** 91 | * Get head; if empty wait for specified time at max. 92 | */ 93 | public synchronized Event deQueue(long maxWaitTime) throws InterruptedException { 94 | while (isEmpty()) { 95 | if (maxWaitTime >= 0) { 96 | wait(maxWaitTime); 97 | 98 | // Timed out or woken; if still empty we 99 | // had bad luck and return failure. 100 | if (isEmpty()) { 101 | return null; 102 | } 103 | } else { 104 | // Wait indefinitely for something in queue. 105 | wait(); 106 | } 107 | } 108 | 109 | // Dequeue item 110 | Event result = fetchNext(); 111 | 112 | // Notify possible wait()-ing enQueue()-ers 113 | notifyAll(); 114 | 115 | // Return dequeued item 116 | return result; 117 | } 118 | 119 | /** 120 | * Get all queued Events. 121 | */ 122 | public synchronized Event[] deQueueAll(long maxWaitTime) throws InterruptedException { 123 | while (isEmpty()) { 124 | if (maxWaitTime >= 0) { 125 | wait(maxWaitTime); 126 | 127 | // Timed out or woken; if still empty we 128 | // had bad luck and return failure. 129 | if (isEmpty()) { 130 | return null; 131 | } 132 | } else { 133 | // Wait indefinitely for something in queue. 134 | wait(); 135 | } 136 | } 137 | 138 | // Dequeue all items item 139 | Event[] events = new Event[getSize()]; 140 | for (int i = 0; i < events.length; i++) { 141 | events[i] = fetchNext(); 142 | } 143 | 144 | // Notify possible wait()-ing enQueue()-ers 145 | notifyAll(); 146 | 147 | // Return dequeued item 148 | return events; 149 | } 150 | 151 | public synchronized int getSize() { 152 | return (rear >= front) ? (rear - front) : (capacity - front + rear); 153 | } 154 | 155 | /** 156 | * Is the queue empty ? 157 | */ 158 | public synchronized boolean isEmpty() { 159 | return front == rear; 160 | } 161 | 162 | /** 163 | * Is the queue full ? 164 | */ 165 | public synchronized boolean isFull() { 166 | return (next(rear) == front); 167 | } 168 | 169 | /** 170 | * Circular counter. 171 | */ 172 | private int next(int index) { 173 | return (index + 1 < capacity ? index + 1 : 0); 174 | } 175 | 176 | /** 177 | * Circular counter. 178 | */ 179 | private Event fetchNext() { 180 | Event temp = queue[front]; 181 | queue[front] = null; 182 | front = next(front); 183 | return temp; 184 | } 185 | 186 | public static void p(String s) { 187 | System.out.println(s); 188 | } 189 | 190 | public static void main(String[] args) { 191 | EventQueue q = new EventQueue(8); 192 | Event event = new Event("t"); 193 | try { 194 | q.enQueue(event); 195 | p("(1) size = " + q.getSize()); 196 | q.enQueue(event); 197 | p("(2) size = " + q.getSize()); 198 | q.deQueue(); 199 | p("(1) size = " + q.getSize()); 200 | q.deQueue(); 201 | p("(0) size = " + q.getSize()); 202 | 203 | q.enQueue(event); 204 | q.enQueue(event); 205 | q.enQueue(event); 206 | p("(3) size = " + q.getSize()); 207 | q.deQueue(); 208 | p("(2) size = " + q.getSize()); 209 | q.enQueue(event); 210 | q.enQueue(event); 211 | q.enQueue(event); 212 | p("(5) size = " + q.getSize()); 213 | q.enQueue(event); 214 | q.enQueue(event); 215 | p("(7) size = " + q.getSize()); 216 | q.deQueue(); 217 | q.deQueue(); 218 | q.deQueue(); 219 | p("(4) size = " + q.getSize()); 220 | q.deQueue(); 221 | q.deQueue(); 222 | q.deQueue(); 223 | ; 224 | q.deQueue(); 225 | p("(0) size = " + q.getSize()); 226 | 227 | q.enQueue(event); 228 | q.enQueue(event); 229 | q.enQueue(event); 230 | q.enQueue(event); 231 | q.enQueue(event); 232 | p("(5) size = " + q.getSize()); 233 | 234 | q.deQueue(); 235 | q.deQueue(); 236 | q.deQueue(); 237 | ; 238 | q.deQueue(); 239 | p("(1) size = " + q.getSize()); 240 | } catch (InterruptedException ie) { 241 | } 242 | } 243 | } 244 | 245 | /* 246 | * $Log: EventQueue.java,v $ 247 | * Revision 1.3 2007/11/23 14:33:07 justb 248 | * core classes now configurable through factory 249 | * 250 | * Revision 1.2 2005/02/21 11:50:46 justb 251 | * ohase1 of refactoring Subscriber into Session/Controller/Subscriber 252 | * 253 | * Revision 1.1 2005/02/18 10:07:23 justb 254 | * many renamings of classes (make names compact) 255 | * 256 | * Revision 1.6 2005/02/16 12:16:16 justb 257 | * added support for "poll" mode 258 | * 259 | * Revision 1.5 2005/01/13 14:47:15 justb 260 | * control evt: send response on same (control) connection 261 | * 262 | * Revision 1.4 2004/09/03 22:35:37 justb 263 | * Almost complete rewrite, just checking in now 264 | * 265 | * Revision 1.3 2003/08/15 08:37:40 justb 266 | * fix/add Copyright+LGPL file headers and footers 267 | * 268 | * 269 | */ 270 | -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/core/EventSource.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2000 Just Objects B.V. 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | 4 | package nl.justobjects.pushlet.core; 5 | 6 | /** 7 | * Abstract Event source from which Events are pulled. 8 | * 9 | * @version $Id: EventSource.java,v 1.7 2007/11/23 14:33:07 justb Exp $ 10 | * @author Just van den Broecke - Just Objects © 11 | **/ 12 | 13 | /** 14 | * Interface for specifc Event(Pull/Push)Sources. 15 | */ 16 | public interface EventSource { 17 | /** 18 | * Activate the event source. 19 | */ 20 | public void activate(); 21 | 22 | /** 23 | * Deactivate the event source. 24 | */ 25 | public void passivate(); 26 | 27 | /** 28 | * Halt the event source. 29 | */ 30 | public void stop(); 31 | } 32 | 33 | /* 34 | * $Log: EventSource.java,v $ 35 | * Revision 1.7 2007/11/23 14:33:07 justb 36 | * core classes now configurable through factory 37 | * 38 | * Revision 1.6 2005/02/21 11:50:46 justb 39 | * ohase1 of refactoring Subscriber into Session/Controller/Subscriber 40 | * 41 | * Revision 1.5 2005/02/18 10:07:23 justb 42 | * many renamings of classes (make names compact) 43 | * 44 | * Revision 1.4 2004/09/03 22:35:37 justb 45 | * Almost complete rewrite, just checking in now 46 | * 47 | * Revision 1.3 2003/08/15 08:37:40 justb 48 | * fix/add Copyright+LGPL file headers and footers 49 | * 50 | * Revision 1.2 2003/05/18 16:15:08 justb 51 | * support for XML encoded Events 52 | * 53 | * Revision 1.1.1.1 2002/09/24 21:02:31 justb 54 | * import to sourceforge 55 | * 56 | * Revision 1.1.1.1 2002/09/20 22:48:17 justb 57 | * import to SF 58 | * 59 | * Revision 1.1.1.1 2002/09/20 14:19:03 justb 60 | * first import into SF 61 | * 62 | * Revision 1.3 2002/04/15 20:42:41 just 63 | * reformatting and renaming GuardedQueue to EventQueue 64 | * 65 | * Revision 1.2 2000/08/21 20:48:29 just 66 | * added CVS log and id tags plus copyrights 67 | * 68 | * 69 | */ 70 | 71 | -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/core/EventSourceManager.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2000 Just Objects B.V. 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | 4 | package nl.justobjects.pushlet.core; 5 | 6 | import nl.justobjects.pushlet.util.Log; 7 | import nl.justobjects.pushlet.util.Sys; 8 | 9 | import java.util.Enumeration; 10 | import java.util.Properties; 11 | import java.util.Vector; 12 | import java.io.File; 13 | 14 | /** 15 | * Maintains lifecycle of event sources. 16 | * 17 | * @author Just van den Broecke - Just Objects © 18 | * @version $Id: EventSourceManager.java,v 1.14 2007/11/10 13:44:02 justb Exp $ 19 | */ 20 | public class EventSourceManager { 21 | private static Vector eventSources = new Vector(0); 22 | private static final String PROPERTIES_FILE = "sources.properties"; 23 | 24 | /** 25 | * Initialize event sources from properties file. 26 | */ 27 | public static void start(String aDirPath) { 28 | // Load Event sources using properties file. 29 | Log.info("EventSourceManager: start"); 30 | 31 | Properties properties = null; 32 | 33 | try { 34 | properties = Sys.loadPropertiesResource(PROPERTIES_FILE); 35 | } catch (Throwable t) { 36 | // Try from provided dir (e.g. WEB_INF/pushlet.properties) 37 | String filePath = aDirPath + File.separator + PROPERTIES_FILE; 38 | Log.info("EventSourceManager: cannot load " + PROPERTIES_FILE + " from classpath, will try from " + filePath); 39 | 40 | try { 41 | properties = Sys.loadPropertiesFile(filePath); 42 | } catch (Throwable t2) { 43 | Log.fatal("EventSourceManager: cannot load properties file from " + filePath, t); 44 | 45 | // Give up 46 | Log.warn("EventSourceManager: not starting local event sources (maybe that is what you want)"); 47 | return; 48 | } 49 | } 50 | 51 | // Create event source collection 52 | eventSources = new Vector(properties.size()); 53 | 54 | // Add the configured sources 55 | for (Enumeration e = properties.keys(); e.hasMoreElements();) { 56 | String nextKey = (String) e.nextElement(); 57 | String nextClass = properties.getProperty(nextKey); 58 | EventSource nextEventSource = null; 59 | try { 60 | nextEventSource = (EventSource) Class.forName(nextClass).newInstance(); 61 | Log.info("created EventSource: key=" + nextKey + " class=" + nextClass); 62 | eventSources.addElement(nextEventSource); 63 | } catch (Exception ex) { 64 | Log.warn("Cannot create EventSource: class=" + nextClass, ex); 65 | } 66 | } 67 | 68 | activate(); 69 | } 70 | 71 | /** 72 | * Activate all event sources. 73 | */ 74 | public static void activate() { 75 | Log.info("Activating " + eventSources.size() + " EventSources"); 76 | for (int i = 0; i < eventSources.size(); i++) { 77 | ((EventSource) eventSources.elementAt(i)).activate(); 78 | } 79 | Log.info("EventSources activated"); 80 | } 81 | 82 | /** 83 | * Deactivate all event sources. 84 | */ 85 | public static void passivate() { 86 | Log.info("Passivating " + eventSources.size() + " EventSources"); 87 | for (int i = 0; i < eventSources.size(); i++) { 88 | ((EventSource) eventSources.elementAt(i)).passivate(); 89 | } 90 | Log.info("EventSources passivated"); 91 | } 92 | 93 | /** 94 | * Halt event sources. 95 | */ 96 | public static void stop() { 97 | Log.info("Stopping " + eventSources.size() + " EventSources..."); 98 | for (int i = 0; i < eventSources.size(); i++) { 99 | ((EventSource) eventSources.elementAt(i)).stop(); 100 | } 101 | Log.info("EventSources stopped"); 102 | } 103 | 104 | } 105 | 106 | /* 107 | * $Log: EventSourceManager.java,v $ 108 | * Revision 1.14 2007/11/10 13:44:02 justb 109 | * pushlet.properties and sources.properties can now also be put under WEB-INF 110 | * 111 | * Revision 1.13 2005/02/21 11:50:46 justb 112 | * ohase1 of refactoring Subscriber into Session/Controller/Subscriber 113 | * 114 | * Revision 1.12 2005/02/18 12:36:47 justb 115 | * changes for renaming and configurability 116 | * 117 | * Revision 1.11 2005/02/18 10:07:23 justb 118 | * many renamings of classes (make names compact) 119 | * 120 | * Revision 1.10 2005/02/15 13:29:49 justb 121 | * use Sys.loadPropertiesResource() 122 | * 123 | * Revision 1.9 2004/09/20 22:01:38 justb 124 | * more changes for new protocol 125 | * 126 | * Revision 1.8 2004/09/03 22:35:37 justb 127 | * Almost complete rewrite, just checking in now 128 | * 129 | * Revision 1.7 2004/08/15 16:00:15 justb 130 | * enhancements to pull mode 131 | * 132 | * Revision 1.6 2004/08/13 23:36:05 justb 133 | * rewrite of Pullet into Pushlet "pull" mode 134 | * 135 | * Revision 1.5 2004/08/12 13:18:54 justb 136 | * cosmetic changes 137 | * 138 | * Revision 1.4 2003/08/15 08:37:40 justb 139 | * fix/add Copyright+LGPL file headers and footers 140 | * 141 | * Revision 1.3 2003/08/12 09:41:35 justb 142 | * replace static initalizer with explicit init() 143 | * 144 | * Revision 1.2 2003/05/18 16:15:08 justb 145 | * support for XML encoded Events 146 | * 147 | * Revision 1.1.1.1 2002/09/24 21:02:31 justb 148 | * import to sourceforge 149 | * 150 | * Revision 1.1.1.1 2002/09/20 22:48:17 justb 151 | * import to SF 152 | * 153 | * Revision 1.1.1.1 2002/09/20 14:19:03 justb 154 | * first import into SF 155 | * 156 | * Revision 1.5 2002/04/15 20:42:41 just 157 | * reformatting and renaming GuardedQueue to EventQueue 158 | * 159 | * Revision 1.4 2000/10/30 14:15:47 just 160 | * no message 161 | * 162 | * Revision 1.3 2000/08/31 08:26:54 just 163 | * Changed classloader that loads eventsources.properties to use EventSourceManager's classloader 164 | * 165 | * Revision 1.2 2000/08/21 20:48:29 just 166 | * added CVS log and id tags plus copyrights 167 | * 168 | * 169 | */ 170 | -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/core/Protocol.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2004 Just Objects B.V. 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | 4 | package nl.justobjects.pushlet.core; 5 | 6 | 7 | /** 8 | * Constants for Pushlet protocols. 9 | * 10 | * @author Just van den Broecke - Just Objects © 11 | * @version $Id: Protocol.java,v 1.15 2007/11/23 14:33:07 justb Exp $ 12 | */ 13 | public interface Protocol { 14 | /** 15 | * Default URI . 16 | */ 17 | public static final String DEFAULT_SERVLET_URI = "/pushlet/pushlet.srv"; 18 | 19 | // 20 | // Common protocol header/parameter names 21 | // 22 | 23 | /** 24 | * Event type (join, leave, data, subscribe etc) . 25 | */ 26 | public static final String P_EVENT = "p_event"; 27 | 28 | /** 29 | * Time in seconds since 1970 30 | */ 31 | public static final String P_TIME = "p_time"; 32 | 33 | /** 34 | * Event sequence number, numbers per-client. 35 | */ 36 | public static final String P_SEQ = "p_seq"; 37 | 38 | /** 39 | * Subject (topic) of data event. 40 | */ 41 | public static final String P_SUBJECT = "p_subject"; 42 | 43 | /** 44 | * Originator of Event. 45 | */ 46 | public static final String P_FROM = "p_from"; 47 | 48 | /** 49 | * Addressee of Event, subject or client p_id. 50 | */ 51 | public static final String P_TO = "p_to"; 52 | 53 | /** 54 | * Identifier for client instance within server. 55 | */ 56 | public static final String P_ID = "p_id"; 57 | 58 | /** 59 | * Subscription id, identifies single subscription. 60 | */ 61 | public static final String P_SUBSCRIPTION_ID = "p_sid"; 62 | 63 | /** 64 | * Format to receive events 65 | */ 66 | public static final String P_FORMAT = "p_format"; 67 | 68 | /** 69 | * Protocol mode. 70 | */ 71 | public static final String P_MODE = "p_mode"; 72 | 73 | /** 74 | * Reason for errors. 75 | */ 76 | public static final String P_REASON = "p_reason"; 77 | 78 | /** 79 | * URL attribute. 80 | */ 81 | public static final String P_URL = "p_url"; 82 | 83 | /** 84 | * Wait attribute. 85 | */ 86 | public static final String P_WAIT = "p_wait"; 87 | 88 | /** 89 | * Subscription label, may be used to return user-specific 90 | * token with a data event, e.g. the name of a function for a callback. 91 | */ 92 | public static final String P_SUBSCRIPTION_LABEL = "p_label"; 93 | 94 | // 95 | // Event values with direction for P_EVENT (C=client, S=server) 96 | // 97 | 98 | /** 99 | * C-->S Request to join server. 100 | */ 101 | public static final String E_JOIN = "join"; 102 | 103 | /** 104 | * S-->C Acknowledgement of join. 105 | */ 106 | public static final String E_JOIN_ACK = "join-ack"; 107 | 108 | /** 109 | * C-->S Request to join server. 110 | */ 111 | public static final String E_JOIN_LISTEN = "join-listen"; 112 | 113 | /** 114 | * S-->C Acknowledgement of join. 115 | */ 116 | public static final String E_JOIN_LISTEN_ACK = "join-listen-ack"; 117 | 118 | /** 119 | * C-->S Client starts listening. 120 | */ 121 | public static final String E_LISTEN = "listen"; 122 | 123 | /** 124 | * S-->C Ack of listen. 125 | */ 126 | public static final String E_LISTEN_ACK = "listen-ack"; 127 | 128 | /** 129 | * C-->S Client leaves server. 130 | */ 131 | public static final String E_LEAVE = "leave"; 132 | 133 | /** 134 | * S-->C Ack of leave. 135 | */ 136 | public static final String E_LEAVE_ACK = "leave-ack"; 137 | 138 | /** 139 | * C-->S Publish to subject. 140 | */ 141 | public static final String E_PUBLISH = "publish"; 142 | 143 | /** 144 | * S-->C Publish to subject acknowledge. 145 | */ 146 | public static final String E_PUBLISH_ACK = "publish-ack"; 147 | 148 | /** 149 | * C-->S Subscribe to subject request. 150 | */ 151 | public static final String E_SUBSCRIBE = "subscribe"; 152 | 153 | /** 154 | * S-->C Subscribe to subject acknowledge. 155 | */ 156 | public static final String E_SUBSCRIBE_ACK = "subscribe-ack"; 157 | 158 | /** 159 | * C-->S Unsubscribe from subject request. 160 | */ 161 | public static final String E_UNSUBSCRIBE = "unsubscribe"; 162 | 163 | /** 164 | * S--C Unsubscribe from subject acknowledge. 165 | */ 166 | public static final String E_UNSUBSCRIBE_ACK = "unsubscribe-ack"; 167 | 168 | /** 169 | * S-->C Client error response, transitional error. 170 | */ 171 | public static final String E_NACK = "nack"; 172 | 173 | /** 174 | * S-->C Client should abort, permanent error. 175 | */ 176 | public static final String E_ABORT = "abort"; 177 | 178 | /** 179 | * S-->C Data. 180 | */ 181 | public static final String E_DATA = "data"; 182 | 183 | /** 184 | * S-->C or C-->S Heartbeat. 185 | */ 186 | public static final String E_HEARTBEAT = "hb"; 187 | 188 | /** 189 | * S-->C S-->C or C-->S Heartbeat confirmed. 190 | */ 191 | public static final String E_HEARTBEAT_ACK = "hb-ack"; 192 | 193 | /** 194 | * S-->C or C-->S client refresh of data channel. 195 | */ 196 | public static final String E_REFRESH = "refresh"; 197 | 198 | /** 199 | * S-->C client should refresh data channel. 200 | */ 201 | public static final String E_REFRESH_ACK = "refresh-ack"; 202 | 203 | // 204 | // Values for P_FORMAT parameter 205 | // 206 | 207 | /** 208 | * JavaScript callback. 209 | */ 210 | public static String FORMAT_JAVASCRIPT = "js"; 211 | 212 | /** 213 | * Java serialized object. 214 | */ 215 | public static String FORMAT_SERIALIZED_JAVA_OBJECT = "ser"; 216 | 217 | /** 218 | * Stream of XML documents. 219 | */ 220 | public static String FORMAT_XML = "xml"; 221 | 222 | /** 223 | * Single XML document containing zero or more events. 224 | */ 225 | public static String FORMAT_XML_STRICT = "xml-strict"; 226 | 227 | // 228 | // Values for P_MODE parameter 229 | // 230 | public static final String MODE_STREAM = "stream"; 231 | public static final String MODE_PULL = "pull"; 232 | public static final String MODE_POLL = "poll"; 233 | 234 | // 235 | // Values for special/reserved subjects 236 | // TODO: use these to publish events when clients do these actions 237 | // TODO: Dispatcher may intercept these subjects to send cached events 238 | // 239 | public static final String SUBJECT_META = "/meta"; 240 | public static final String SUBJECT_META_SUBS = SUBJECT_META + "/subs"; 241 | public static final String SUBJECT_META_JOINS = SUBJECT_META + "/joins"; 242 | 243 | 244 | } 245 | 246 | /* 247 | * $Log: Protocol.java,v $ 248 | * Revision 1.15 2007/11/23 14:33:07 justb 249 | * core classes now configurable through factory 250 | * 251 | * Revision 1.14 2006/10/19 12:33:40 justb 252 | * add atomic join-listen support (one request) 253 | * 254 | * Revision 1.13 2005/05/06 19:44:00 justb 255 | * added xml-strict format 256 | * 257 | * Revision 1.12 2005/02/28 13:05:59 justb 258 | * introduced join-listen protocol service 259 | * 260 | * Revision 1.11 2005/02/28 12:45:59 justb 261 | * introduced Command class 262 | * 263 | * Revision 1.10 2005/02/16 12:16:17 justb 264 | * added support for "poll" mode 265 | * 266 | * Revision 1.9 2005/01/24 22:46:02 justb 267 | * getting safari to work 268 | * 269 | * Revision 1.8 2005/01/24 13:42:00 justb 270 | * new protocol changes (p_listen) 271 | * 272 | * Revision 1.7 2005/01/18 16:47:10 justb 273 | * protocol changes for v2 and publishing from pushlet client 274 | * 275 | * Revision 1.6 2005/01/13 14:47:15 justb 276 | * control evt: send response on same (control) connection 277 | * 278 | * Revision 1.5 2004/10/24 13:52:52 justb 279 | * small fixes in client lib 280 | * 281 | * Revision 1.4 2004/10/24 12:58:18 justb 282 | * revised client and test classes for new protocol 283 | * 284 | * Revision 1.3 2004/09/20 22:01:38 justb 285 | * more changes for new protocol 286 | * 287 | * Revision 1.2 2004/09/03 22:35:37 justb 288 | * Almost complete rewrite, just checking in now 289 | * 290 | * Revision 1.1 2004/09/03 21:02:20 justb 291 | * make more formalized protocol 292 | * 293 | * 294 | */ 295 | -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/core/SerializedAdapter.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2000 Just Objects B.V. 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | 4 | package nl.justobjects.pushlet.core; 5 | 6 | import javax.servlet.http.HttpServletResponse; 7 | import java.io.IOException; 8 | import java.io.ObjectOutputStream; 9 | 10 | /** 11 | * Implementation of ClientAdapter that sends Events as serialized objects. 12 | *

13 | * NOTE: You are discouraged to use this adapter, since it is Java-only 14 | * and may have JVM-specific problems. Far better choice is to use XML 15 | * and the XMLAdapter. 16 | * 17 | * @author Just van den Broecke - Just Objects © 18 | * @version $Id: SerializedAdapter.java,v 1.4 2007/11/23 14:33:07 justb Exp $ 19 | */ 20 | class SerializedAdapter implements ClientAdapter { 21 | private ObjectOutputStream out = null; 22 | public static final String CONTENT_TYPE = "application/x-java-serialized-object"; 23 | private HttpServletResponse servletRsp; 24 | 25 | /** 26 | * Initialize. 27 | */ 28 | public SerializedAdapter(HttpServletResponse aServletResponse) { 29 | servletRsp = aServletResponse; 30 | } 31 | 32 | public void start() throws IOException { 33 | 34 | servletRsp.setContentType(CONTENT_TYPE); 35 | 36 | // Use a serialized object output stream 37 | out = new ObjectOutputStream(servletRsp.getOutputStream()); 38 | 39 | // Don't need this further 40 | servletRsp = null; 41 | } 42 | 43 | /** 44 | * Push Event to client. 45 | */ 46 | public void push(Event anEvent) throws IOException { 47 | out.writeObject(anEvent); 48 | 49 | out.flush(); 50 | } 51 | 52 | 53 | public void stop() throws IOException { 54 | } 55 | } 56 | 57 | /* 58 | * $Log: SerializedAdapter.java,v $ 59 | * Revision 1.4 2007/11/23 14:33:07 justb 60 | * core classes now configurable through factory 61 | * 62 | * Revision 1.3 2005/02/28 12:45:59 justb 63 | * introduced Command class 64 | * 65 | * Revision 1.2 2005/02/21 11:50:46 justb 66 | * ohase1 of refactoring Subscriber into Session/Controller/Subscriber 67 | * 68 | * Revision 1.1 2005/02/18 10:07:23 justb 69 | * many renamings of classes (make names compact) 70 | * 71 | * Revision 1.4 2004/09/03 22:35:37 justb 72 | * Almost complete rewrite, just checking in now 73 | * 74 | * Revision 1.3 2003/08/15 08:37:40 justb 75 | * fix/add Copyright+LGPL file headers and footers 76 | * 77 | * Revision 1.2 2003/05/18 16:13:48 justb 78 | * fixed blocking for java.net.URL with HTTP/1.1 (JVMs > 1.1) 79 | * 80 | * Revision 1.1.1.1 2002/09/24 21:02:31 justb 81 | * import to sourceforge 82 | * 83 | * Revision 1.1.1.1 2002/09/20 22:48:18 justb 84 | * import to SF 85 | * 86 | * Revision 1.1.1.1 2002/09/20 14:19:03 justb 87 | * first import into SF 88 | * 89 | * Revision 1.5 2002/04/15 20:42:41 just 90 | * reformatting and renaming GuardedQueue to EventQueue 91 | * 92 | * Revision 1.4 2000/12/27 22:39:35 just 93 | * no message 94 | * 95 | * Revision 1.3 2000/08/21 20:48:29 just 96 | * added CVS log and id tags plus copyrights 97 | * 98 | * 99 | */ 100 | -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/core/Session.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2000 Just Objects B.V. 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | 4 | package nl.justobjects.pushlet.core; 5 | 6 | import nl.justobjects.pushlet.util.Log; 7 | import nl.justobjects.pushlet.util.PushletException; 8 | 9 | /** 10 | * Represents client pushlet session state. 11 | * 12 | * @author Just van den Broecke - Just Objects © 13 | * @version $Id: Session.java,v 1.8 2007/11/23 14:33:07 justb Exp $ 14 | */ 15 | public class Session implements Protocol, ConfigDefs { 16 | private Controller controller; 17 | private Subscriber subscriber; 18 | 19 | private String userAgent; 20 | private long LEASE_TIME_MILLIS = Config.getLongProperty(SESSION_TIMEOUT_MINS) * 60 * 1000; 21 | private volatile long timeToLive = LEASE_TIME_MILLIS; 22 | 23 | public static String[] FORCED_PULL_AGENTS = Config.getProperty(LISTEN_FORCE_PULL_AGENTS).split(","); 24 | 25 | private String address = "unknown"; 26 | private String format = FORMAT_XML; 27 | 28 | private String id; 29 | 30 | /** 31 | * Protected constructor as we create through factory method. 32 | */ 33 | protected Session() { 34 | } 35 | 36 | /** 37 | * Create instance through factory method. 38 | * 39 | * @param anId a session id 40 | * @return a Session object (or derived) 41 | * @throws PushletException exception, usually misconfiguration 42 | */ 43 | public static Session create(String anId) throws PushletException { 44 | Session session; 45 | try { 46 | session = (Session) Config.getClass(SESSION_CLASS, "nl.justobjects.pushlet.core.Session").newInstance(); 47 | } catch (Throwable t) { 48 | throw new PushletException("Cannot instantiate Session from config", t); 49 | } 50 | 51 | // Init session 52 | session.id = anId; 53 | session.controller = Controller.create(session); 54 | session.subscriber = Subscriber.create(session); 55 | return session; 56 | } 57 | 58 | /** 59 | * Return (remote) Subscriber client's IP address. 60 | */ 61 | public String getAddress() { 62 | return address; 63 | } 64 | 65 | /** 66 | * Return command controller. 67 | */ 68 | public Controller getController() { 69 | return controller; 70 | } 71 | 72 | /** 73 | * Return Event format to send to client. 74 | */ 75 | public String getFormat() { 76 | return format; 77 | } 78 | 79 | /** 80 | * Return (remote) Subscriber client's unique id. 81 | */ 82 | public String getId() { 83 | return id; 84 | } 85 | 86 | /** 87 | * Return subscriber. 88 | */ 89 | public Subscriber getSubscriber() { 90 | return subscriber; 91 | } 92 | 93 | /** 94 | * Return remote HTTP User-Agent. 95 | */ 96 | public String getUserAgent() { 97 | return userAgent; 98 | } 99 | 100 | /** 101 | * Set address. 102 | */ 103 | protected void setAddress(String anAddress) { 104 | address = anAddress; 105 | } 106 | 107 | /** 108 | * Set event format to encode. 109 | */ 110 | protected void setFormat(String aFormat) { 111 | format = aFormat; 112 | } 113 | 114 | /** 115 | * Set client HTTP UserAgent. 116 | */ 117 | public void setUserAgent(String aUserAgent) { 118 | userAgent = aUserAgent; 119 | } 120 | 121 | /** 122 | * Decrease time to live. 123 | */ 124 | public void age(long aDeltaMillis) { 125 | timeToLive -= aDeltaMillis; 126 | } 127 | 128 | /** 129 | * Has session timed out? 130 | */ 131 | public boolean isExpired() { 132 | return timeToLive <= 0; 133 | } 134 | 135 | /** 136 | * Keep alive by resetting TTL. 137 | */ 138 | public void kick() { 139 | timeToLive = LEASE_TIME_MILLIS; 140 | } 141 | 142 | public void start() { 143 | SessionManager.getInstance().addSession(this); 144 | } 145 | 146 | public void stop() { 147 | subscriber.stop(); 148 | SessionManager.getInstance().removeSession(this); 149 | } 150 | 151 | /** 152 | * Info. 153 | */ 154 | public void info(String s) { 155 | Log.info("S-" + this + ": " + s); 156 | } 157 | 158 | /** 159 | * Exceptional print util. 160 | */ 161 | public void warn(String s) { 162 | Log.warn("S-" + this + ": " + s); 163 | } 164 | 165 | /** 166 | * Exceptional print util. 167 | */ 168 | public void debug(String s) { 169 | Log.debug("S-" + this + ": " + s); 170 | } 171 | 172 | public String toString() { 173 | return getAddress() + "[" + getId() + "]"; 174 | } 175 | } 176 | 177 | /* 178 | * $Log: Session.java,v $ 179 | * Revision 1.8 2007/11/23 14:33:07 justb 180 | * core classes now configurable through factory 181 | * 182 | * Revision 1.7 2005/02/28 15:58:05 justb 183 | * added SimpleListener example 184 | * 185 | * Revision 1.6 2005/02/28 12:45:59 justb 186 | * introduced Command class 187 | * 188 | * Revision 1.5 2005/02/28 09:14:55 justb 189 | * sessmgr/dispatcher factory/singleton support 190 | * 191 | * Revision 1.4 2005/02/25 15:13:01 justb 192 | * session id generation more robust 193 | * 194 | * Revision 1.3 2005/02/21 16:59:08 justb 195 | * SessionManager and session lease introduced 196 | * 197 | * Revision 1.2 2005/02/21 12:32:28 justb 198 | * fixed publish event in Controller 199 | * 200 | * Revision 1.1 2005/02/21 11:50:46 justb 201 | * ohase1 of refactoring Subscriber into Session/Controller/Subscriber 202 | * 203 | 204 | * 205 | */ 206 | -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/core/SessionManager.java: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushelp/Pushlet-UserUnicast/b423f8e1cde4ae761d16cf55d732a16d5d540d1a/src/nl/justobjects/pushlet/core/SessionManager.java -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/core/Subscriber.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2000 Just Objects B.V. 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | 4 | package nl.justobjects.pushlet.core; 5 | 6 | import nl.justobjects.pushlet.util.PushletException; 7 | import nl.justobjects.pushlet.util.Rand; 8 | import nl.justobjects.pushlet.util.Sys; 9 | 10 | import java.util.Collections; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | import java.net.URLEncoder; 14 | 15 | /** 16 | * Handles data channel between dispatcher and client. 17 | * 18 | * @author Just van den Broecke - Just Objects © 19 | * @version $Id: Subscriber.java,v 1.26 2007/11/23 14:33:07 justb Exp $ 20 | */ 21 | public class Subscriber implements Protocol, ConfigDefs { 22 | private Session session; 23 | 24 | /** 25 | * Blocking queue. 26 | */ 27 | private EventQueue eventQueue = new EventQueue(Config.getIntProperty(QUEUE_SIZE)); 28 | 29 | /** 30 | * URL to be used in refresh requests in pull/poll modes. 31 | */ 32 | private long queueReadTimeoutMillis = Config.getLongProperty(QUEUE_READ_TIMEOUT_MILLIS); 33 | private long queueWriteTimeoutMillis = Config.getLongProperty(QUEUE_WRITE_TIMEOUT_MILLIS); 34 | private long refreshTimeoutMillis = Config.getLongProperty(PULL_REFRESH_TIMEOUT_MILLIS); 35 | volatile long lastAlive = Sys.now(); 36 | 37 | /** 38 | * Map of active subscriptions, keyed by their subscription id. 39 | */ 40 | private Map subscriptions = Collections.synchronizedMap(new HashMap(3)); 41 | 42 | /** 43 | * Are we able to accept/send events ?. 44 | */ 45 | private volatile boolean active; 46 | 47 | /** 48 | * Transfer mode (stream, pull, poll). 49 | */ 50 | private String mode; 51 | 52 | 53 | /** 54 | * Protected constructor as we create through factory method. 55 | */ 56 | protected Subscriber() { 57 | } 58 | 59 | /** 60 | * Create instance through factory method. 61 | * 62 | * @param aSession the parent Session 63 | * @return a Subscriber object (or derived) 64 | * @throws PushletException exception, usually misconfiguration 65 | */ 66 | public static Subscriber create(Session aSession) throws PushletException { 67 | Subscriber subscriber; 68 | try { 69 | subscriber = (Subscriber) Config.getClass(SUBSCRIBER_CLASS, "nl.justobjects.pushlet.core.Subscriber").newInstance(); 70 | } catch (Throwable t) { 71 | throw new PushletException("Cannot instantiate Subscriber from config", t); 72 | } 73 | 74 | subscriber.session = aSession; 75 | return subscriber; 76 | } 77 | 78 | public void start() { 79 | active = true; 80 | } 81 | 82 | public void stop() { 83 | removeSubscriptions(); 84 | active = false; 85 | } 86 | 87 | public void bailout() { 88 | session.stop(); 89 | } 90 | 91 | /** 92 | * Are we still active to handle events. 93 | */ 94 | public boolean isActive() { 95 | return active; 96 | } 97 | 98 | /** 99 | * Return client session. 100 | */ 101 | public Session getSession() { 102 | return session; 103 | } 104 | 105 | /** 106 | * Get (session) id. 107 | */ 108 | public String getId() { 109 | return session.getId(); 110 | } 111 | 112 | /** 113 | * Return subscriptions. 114 | */ 115 | public Subscription[] getSubscriptions() { 116 | // todo: Optimize 117 | return (Subscription[]) subscriptions.values().toArray(new Subscription[0]); 118 | } 119 | 120 | /** 121 | * Add a subscription. 122 | */ 123 | public Subscription addSubscription(String aSubject, String aLabel) throws PushletException { 124 | Subscription subscription = Subscription.create(aSubject, aLabel); 125 | subscriptions.put(subscription.getId(), subscription); 126 | info("Subscription added subject=" + aSubject + " sid=" + subscription.getId() + " label=" + aLabel); 127 | return subscription; 128 | } 129 | 130 | /** 131 | * Remove a subscription. 132 | */ 133 | public Subscription removeSubscription(String aSubscriptionId) { 134 | Subscription subscription = (Subscription) subscriptions.remove(aSubscriptionId); 135 | if (subscription == null) { 136 | warn("No subscription found sid=" + aSubscriptionId); 137 | return null; 138 | } 139 | info("Subscription removed subject=" + subscription.getSubject() + " sid=" + subscription.getId() + " label=" + subscription.getLabel()); 140 | return subscription; 141 | } 142 | 143 | /** 144 | * Remove all subscriptions. 145 | */ 146 | public void removeSubscriptions() { 147 | subscriptions.clear(); 148 | } 149 | 150 | public String getMode() { 151 | return mode; 152 | } 153 | 154 | public void setMode(String aMode) { 155 | mode = aMode; 156 | } 157 | 158 | public long getRefreshTimeMillis() { 159 | String minWaitProperty = PULL_REFRESH_WAIT_MIN_MILLIS; 160 | String maxWaitProperty = PULL_REFRESH_WAIT_MAX_MILLIS; 161 | if (mode.equals((MODE_POLL))) { 162 | minWaitProperty = POLL_REFRESH_WAIT_MIN_MILLIS; 163 | maxWaitProperty = POLL_REFRESH_WAIT_MAX_MILLIS; 164 | 165 | } 166 | return Rand.randomLong(Config.getLongProperty(minWaitProperty), 167 | Config.getLongProperty(maxWaitProperty)); 168 | } 169 | 170 | /** 171 | * Get events from queue and push to client. 172 | */ 173 | public void fetchEvents(Command aCommand) throws PushletException { 174 | 175 | String refreshURL = aCommand.httpReq.getRequestURI() + "?" + P_ID + "=" + session.getId() + "&" + P_EVENT + "=" + E_REFRESH; 176 | 177 | // This is the only thing required to support "poll" mode 178 | if (mode.equals(MODE_POLL)) { 179 | queueReadTimeoutMillis = 0; 180 | refreshTimeoutMillis = Config.getLongProperty(POLL_REFRESH_TIMEOUT_MILLIS); 181 | } 182 | 183 | // Required for fast bailout (tomcat) 184 | aCommand.httpRsp.setBufferSize(128); 185 | 186 | // Try to prevent caching in any form. 187 | aCommand.sendResponseHeaders(); 188 | 189 | // Let clientAdapter determine how to send event 190 | ClientAdapter clientAdapter = aCommand.getClientAdapter(); 191 | Event responseEvent = aCommand.getResponseEvent(); 192 | try { 193 | clientAdapter.start(); 194 | 195 | // Send first event (usually hb-ack or listen-ack) 196 | clientAdapter.push(responseEvent); 197 | 198 | // In pull/poll mode and when response is listen-ack or join-listen-ack, 199 | // return and force refresh immediately 200 | // such that the client recieves response immediately over this channel. 201 | // This is usually when loading the browser app for the first time 202 | if ((mode.equals(MODE_POLL) || mode.equals(MODE_PULL)) 203 | && responseEvent.getEventType().endsWith(Protocol.E_LISTEN_ACK)) { 204 | sendRefresh(clientAdapter, refreshURL); 205 | 206 | // We should come back later with refresh event... 207 | return; 208 | } 209 | } catch (Throwable t) { 210 | bailout(); 211 | return; 212 | } 213 | 214 | 215 | Event[] events = null; 216 | 217 | // Main loop: as long as connected, get events and push to client 218 | long eventSeqNr = 1; 219 | while (isActive()) { 220 | // Indicate we are still alive 221 | lastAlive = Sys.now(); 222 | 223 | // Update session time to live 224 | session.kick(); 225 | 226 | // Get next events; blocks until timeout or entire contents 227 | // of event queue is returned. Note that "poll" mode 228 | // will return immediately when queue is empty. 229 | try { 230 | // Put heartbeat in queue when starting to listen in stream mode 231 | // This speeds up the return of *_LISTEN_ACK 232 | if (mode.equals(MODE_STREAM) && eventSeqNr == 1) { 233 | eventQueue.enQueue(new Event(E_HEARTBEAT)); 234 | } 235 | 236 | events = eventQueue.deQueueAll(queueReadTimeoutMillis); 237 | } catch (InterruptedException ie) { 238 | warn("interrupted"); 239 | bailout(); 240 | } 241 | 242 | // Send heartbeat when no events received 243 | if (events == null) { 244 | events = new Event[1]; 245 | events[0] = new Event(E_HEARTBEAT); 246 | } 247 | 248 | // ASSERT: one or more events available 249 | 250 | // Send events to client using adapter 251 | // debug("received event count=" + events.length); 252 | for (int i = 0; i < events.length; i++) { 253 | // Check for abort event 254 | if (events[i].getEventType().equals(E_ABORT)) { 255 | warn("Aborting Subscriber"); 256 | bailout(); 257 | } 258 | 259 | // Push next Event to client 260 | try { 261 | // Set sequence number 262 | events[i].setField(P_SEQ, eventSeqNr++); 263 | 264 | // Push to client through client adapter 265 | clientAdapter.push(events[i]); 266 | } catch (Throwable t) { 267 | bailout(); 268 | return; 269 | } 270 | } 271 | 272 | // Force client refresh request in pull or poll modes 273 | if (mode.equals(MODE_PULL) || mode.equals(MODE_POLL)) { 274 | sendRefresh(clientAdapter, refreshURL); 275 | 276 | // Always leave loop in pull/poll mode 277 | break; 278 | } 279 | } 280 | } 281 | 282 | /** 283 | * Determine if we should receive event. 284 | */ 285 | public Subscription match(Event event) { 286 | Subscription[] subscriptions = getSubscriptions(); 287 | for (int i = 0; i < subscriptions.length; i++) { 288 | if (subscriptions[i].match(event)) { 289 | return subscriptions[i]; 290 | } 291 | } 292 | return null; 293 | } 294 | 295 | /** 296 | * Event from Dispatcher: enqueue it. 297 | */ 298 | public void onEvent(Event theEvent) { 299 | if (!isActive()) { 300 | return; 301 | } 302 | 303 | // p("send: queue event: "+theEvent.getSubject()); 304 | 305 | // Check if we had any active continuation for at 306 | // least 'timeOut' millisecs. If the client has left this 307 | // instance there would be no way of knowing otherwise. 308 | long now = Sys.now(); 309 | if (now - lastAlive > refreshTimeoutMillis) { 310 | warn("not alive for at least: " + refreshTimeoutMillis + "ms, leaving..."); 311 | bailout(); 312 | return; 313 | } 314 | 315 | // Put event in queue; leave if queue full 316 | try { 317 | if (!eventQueue.enQueue(theEvent, queueWriteTimeoutMillis)) { 318 | warn("queue full, bailing out..."); 319 | bailout(); 320 | } 321 | 322 | // ASSERTION : Event in queue. 323 | // see fetchEvents() where Events are dequeued and pushed to the client. 324 | } catch (InterruptedException ie) { 325 | bailout(); 326 | } 327 | 328 | } 329 | 330 | /** 331 | * Send refresh command to pull/poll clients. 332 | */ 333 | protected void sendRefresh(ClientAdapter aClientAdapter, String aRefreshURL) { 334 | Event refreshEvent = new Event(E_REFRESH); 335 | 336 | // Set wait time and url for refresh 337 | refreshEvent.setField(P_WAIT, "" + getRefreshTimeMillis()); 338 | refreshEvent.setField(P_URL, aRefreshURL); 339 | 340 | try { 341 | // Push to client through client adapter 342 | aClientAdapter.push(refreshEvent); 343 | 344 | // Stop this round until refresh event 345 | aClientAdapter.stop(); 346 | } catch (Throwable t) { 347 | // Leave on any exception 348 | bailout(); 349 | } 350 | } 351 | 352 | /** 353 | * Info. 354 | */ 355 | protected void info(String s) { 356 | session.info("[Subscriber] " + s); 357 | } 358 | 359 | /** 360 | * Exceptional print util. 361 | */ 362 | protected void warn(String s) { 363 | session.warn("[Subscriber] " + s); 364 | } 365 | 366 | /** 367 | * Exceptional print util. 368 | */ 369 | protected void debug(String s) { 370 | session.debug("[Subscriber] " + s); 371 | } 372 | 373 | 374 | public String toString() { 375 | return session.toString(); 376 | } 377 | } 378 | 379 | /* 380 | * $Log: Subscriber.java,v $ 381 | * Revision 1.26 2007/11/23 14:33:07 justb 382 | * core classes now configurable through factory 383 | * 384 | * Revision 1.25 2007/11/10 15:53:15 justb 385 | * put heartbeat in queue when start fetching events in stream-mode 386 | * 387 | * Revision 1.24 2006/10/19 12:33:40 justb 388 | * add atomic join-listen support (one request) 389 | * 390 | * Revision 1.22 2006/05/06 00:06:28 justb 391 | * first rough version AJAX client 392 | * 393 | * Revision 1.21 2005/02/28 12:45:59 justb 394 | * introduced Command class 395 | * 396 | * Revision 1.20 2005/02/21 16:59:09 justb 397 | * SessionManager and session lease introduced 398 | * 399 | * Revision 1.19 2005/02/21 12:32:28 justb 400 | * fixed publish event in Controller 401 | * 402 | * Revision 1.18 2005/02/21 11:50:46 justb 403 | * ohase1 of refactoring Subscriber into Session/Controller/Subscriber 404 | * 405 | * Revision 1.17 2005/02/20 13:05:32 justb 406 | * removed the Postlet (integrated in Pushlet protocol) 407 | * 408 | * Revision 1.16 2005/02/18 12:36:47 justb 409 | * changes for renaming and configurability 410 | * 411 | * Revision 1.15 2005/02/18 10:07:23 justb 412 | * many renamings of classes (make names compact) 413 | * 414 | * Revision 1.14 2005/02/18 09:54:15 justb 415 | * refactor: rename Publisher Dispatcher and single Subscriber class 416 | * 417 | * Revision 1.13 2005/02/16 14:39:34 justb 418 | * fixed leave handling and added "poll" mode 419 | * 420 | * Revision 1.12 2005/01/24 13:42:00 justb 421 | * new protocol changes (p_listen) 422 | * 423 | * Revision 1.11 2005/01/13 14:47:15 justb 424 | * control evt: send response on same (control) connection 425 | * 426 | * Revision 1.10 2004/10/24 20:50:35 justb 427 | * refine subscription with label and sending sid and label on events 428 | * 429 | * Revision 1.9 2004/10/24 12:58:18 justb 430 | * revised client and test classes for new protocol 431 | * 432 | * Revision 1.8 2004/09/26 21:39:43 justb 433 | * allow multiple subscriptions and out-of-band requests 434 | * 435 | * Revision 1.7 2004/09/20 22:01:38 justb 436 | * more changes for new protocol 437 | * 438 | * Revision 1.6 2004/09/03 22:35:37 justb 439 | * Almost complete rewrite, just checking in now 440 | * 441 | * Revision 1.5 2004/08/13 23:36:05 justb 442 | * rewrite of Pullet into Pushlet "pull" mode 443 | * 444 | * Revision 1.4 2004/03/10 14:01:55 justb 445 | * formatting and *Subscriber refactoring 446 | * 447 | * Revision 1.3 2003/08/15 08:37:40 justb 448 | * fix/add Copyright+LGPL file headers and footers 449 | * 450 | * Revision 1.2 2003/05/18 16:15:08 justb 451 | * support for XML encoded Events 452 | * 453 | * Revision 1.1.1.1 2002/09/24 21:02:32 justb 454 | * import to sourceforge 455 | * 456 | * Revision 1.1.1.1 2002/09/20 22:48:18 justb 457 | * import to SF 458 | * 459 | * Revision 1.1.1.1 2002/09/20 14:19:04 justb 460 | * first import into SF 461 | * 462 | * Revision 1.3 2002/04/15 20:42:41 just 463 | * reformatting and renaming GuardedQueue to EventQueue 464 | * 465 | * Revision 1.2 2000/08/21 20:48:29 just 466 | * added CVS log and id tags plus copyrights 467 | * 468 | * 469 | */ 470 | -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/core/Subscription.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2000 Just Objects B.V. 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | 4 | package nl.justobjects.pushlet.core; 5 | 6 | import nl.justobjects.pushlet.util.Rand; 7 | import nl.justobjects.pushlet.util.PushletException; 8 | 9 | 10 | /** 11 | * Represents single subject subscription 12 | * 13 | * @author Just van den Broecke - Just Objects © 14 | * @version $Id: Subscription.java,v 1.5 2007/11/23 14:33:07 justb Exp $ 15 | */ 16 | public class Subscription implements ConfigDefs { 17 | public static final int ID_SIZE = 5; 18 | public static final String SUBJECT_SEPARATOR = ","; 19 | private String id = Rand.randomName(ID_SIZE); 20 | private String subject; 21 | private String[] subjects; 22 | 23 | /** 24 | * Optional label, a user supplied token. 25 | */ 26 | private String label; 27 | 28 | 29 | /** 30 | * Protected constructor as we create through factory method. 31 | */ 32 | protected Subscription() { 33 | } 34 | 35 | /** 36 | * Create instance through factory method. 37 | * 38 | * @param aSubject the subject (topic). 39 | * @return a Subscription object (or derived) 40 | * @throws nl.justobjects.pushlet.util.PushletException 41 | * exception, usually misconfiguration 42 | */ 43 | public static Subscription create(String aSubject) throws PushletException { 44 | return create(aSubject, null); 45 | } 46 | 47 | /** 48 | * Create instance through factory method. 49 | * 50 | * @param aSubject the subject (topic). 51 | * @param aLabel the subject label (optional). 52 | * @return a Subscription object (or derived) 53 | * @throws nl.justobjects.pushlet.util.PushletException 54 | * exception, usually misconfiguration 55 | */ 56 | public static Subscription create(String aSubject, String aLabel) throws PushletException { 57 | if (aSubject == null || aSubject.length() == 0) { 58 | throw new IllegalArgumentException("Null or emtpy subject"); 59 | } 60 | 61 | Subscription subscription; 62 | try { 63 | subscription = (Subscription) Config.getClass(SUBSCRIPTION_CLASS, "nl.justobjects.pushlet.core.Subscription").newInstance(); 64 | } catch (Throwable t) { 65 | throw new PushletException("Cannot instantiate Subscriber from config", t); 66 | } 67 | 68 | // Init 69 | subscription.subject = aSubject; 70 | 71 | // We may subscribe to multiple subjects by separating 72 | // them with SUBJECT_SEPARATOR, e.g. "/stocks/aex,/system/memory,.."). 73 | subscription.subjects = aSubject.split(SUBJECT_SEPARATOR); 74 | 75 | subscription.label = aLabel; 76 | return subscription; 77 | } 78 | 79 | 80 | public String getId() { 81 | return id; 82 | } 83 | 84 | public String getLabel() { 85 | return label; 86 | } 87 | 88 | public String getSubject() { 89 | return subject; 90 | } 91 | 92 | /** 93 | * Determine if Event matches subscription. 94 | */ 95 | public boolean match(Event event) { 96 | String eventSubject = event.getSubject(); 97 | 98 | // Silly case but check anyway 99 | if (eventSubject == null || eventSubject.length() == 0) { 100 | return false; 101 | } 102 | 103 | // Test if one of the subjects matches 104 | for (int i = 0; i < subjects.length; i++) { 105 | if (eventSubject.startsWith(subjects[i])) { 106 | return true; 107 | } 108 | } 109 | 110 | // No match 111 | return false; 112 | } 113 | } 114 | 115 | /* 116 | * $Log: Subscription.java,v $ 117 | * Revision 1.5 2007/11/23 14:33:07 justb 118 | * core classes now configurable through factory 119 | * 120 | * Revision 1.4 2006/05/06 00:10:11 justb 121 | * various chgs but not too serious... 122 | * 123 | * Revision 1.3 2005/02/16 15:23:10 justb 124 | * multiple subject (, separated) support 125 | * 126 | * Revision 1.2 2005/01/18 16:47:10 justb 127 | * protocol changes for v2 and publishing from pushlet client 128 | * 129 | * Revision 1.1 2004/09/26 21:39:43 justb 130 | * allow multiple subscriptions and out-of-band requests 131 | * 132 | * 133 | */ 134 | -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/core/XMLAdapter.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2000 Just Objects B.V. 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | 4 | package nl.justobjects.pushlet.core; 5 | 6 | import nl.justobjects.pushlet.util.Log; 7 | 8 | import javax.servlet.ServletOutputStream; 9 | import javax.servlet.http.HttpServletResponse; 10 | 11 | import java.io.IOException; 12 | import java.io.PrintWriter; 13 | 14 | /** 15 | * ClientAdapter that sends Events as XML. 16 | * 17 | * @author Just van den Broecke - Just Objects © 18 | * @version $Id: XMLAdapter.java,v 1.7 2007/11/09 13:15:35 justb Exp $ 19 | */ 20 | class XMLAdapter implements ClientAdapter { 21 | /** 22 | * Header for strict XML 23 | */ 24 | // public static final String XML_HEAD = "\n"; 25 | private String contentType = "text/plain;charset=UTF-8"; 26 | // private ServletOutputStream out = null; 27 | private PrintWriter pw = null; 28 | private HttpServletResponse servletRsp; 29 | private boolean strictXML; 30 | 31 | /** 32 | * Initialize. 33 | */ 34 | public XMLAdapter(HttpServletResponse aServletResponse) { 35 | this(aServletResponse, false); 36 | } 37 | 38 | /** 39 | * Initialize. 40 | */ 41 | public XMLAdapter(HttpServletResponse aServletResponse, boolean useStrictXML) { 42 | servletRsp = aServletResponse; 43 | 44 | // Strict XML implies returning a complete XML document 45 | strictXML = useStrictXML; 46 | if (strictXML) { 47 | contentType = "text/xml;charset=UTF-8"; 48 | } 49 | } 50 | 51 | public void start() throws IOException { 52 | 53 | // If content type is plain text 54 | // then this is not a complete XML document, but rather 55 | // a stream of XML documents where each document is 56 | // an Event. In strict XML mode a complete document is returned. 57 | servletRsp.setContentType(contentType); 58 | 59 | // out = servletRsp.getOutputStream(); 60 | pw = servletRsp.getWriter(); 61 | 62 | // Don't need this further 63 | servletRsp = null; 64 | 65 | // Start XML document if strict XML mode 66 | if (strictXML) { 67 | // out.print(""); 68 | pw.print(""); 69 | } 70 | } 71 | 72 | /** 73 | * Force client to refresh the request. 74 | */ 75 | public void push(Event anEvent) throws IOException { 76 | debug("event=" + anEvent); 77 | 78 | // Send the event as XML to the client and flush. 79 | pw.print(anEvent.toXML(strictXML)); 80 | pw.flush(); 81 | // out.print(anEvent.toXML(strictXML)); 82 | // out.flush(); 83 | } 84 | 85 | /** 86 | * No action. 87 | */ 88 | public void stop() throws IOException { 89 | // Close XML document if strict XML mode 90 | if (strictXML) { 91 | pw.print(""); 92 | pw.flush(); 93 | // out.print(""); 94 | // out.flush(); 95 | } 96 | } 97 | 98 | private void debug(String s) { 99 | Log.debug("[XMLAdapter]" + s); 100 | } 101 | } 102 | 103 | /* 104 | * $Log: XMLAdapter.java,v $ 105 | * Revision 1.7 2007/11/09 13:15:35 justb 106 | * add charset=UTF-8 in returned HTTP content types 107 | * 108 | * Revision 1.6 2006/05/15 11:52:53 justb 109 | * updates mainly for AJAX client 110 | * 111 | * Revision 1.5 2006/05/06 00:06:28 justb 112 | * first rough version AJAX client 113 | * 114 | * Revision 1.4 2005/05/06 19:44:00 justb 115 | * added xml-strict format 116 | * 117 | * Revision 1.3 2005/02/28 12:45:59 justb 118 | * introduced Command class 119 | * 120 | * Revision 1.2 2005/02/21 11:50:47 justb 121 | * ohase1 of refactoring Subscriber into Session/Controller/Subscriber 122 | * 123 | * Revision 1.1 2005/02/18 10:07:23 justb 124 | * many renamings of classes (make names compact) 125 | * 126 | * Revision 1.7 2004/09/03 22:35:37 justb 127 | * Almost complete rewrite, just checking in now 128 | * 129 | * Revision 1.6 2004/03/10 14:01:55 justb 130 | * formatting and *Subscriber refactoring 131 | * 132 | * Revision 1.5 2003/08/15 08:37:40 justb 133 | * fix/add Copyright+LGPL file headers and footers 134 | * 135 | * Revision 1.4 2003/08/13 14:00:00 justb 136 | * some testing for applets; no real change 137 | * 138 | * Revision 1.3 2003/08/12 09:57:06 justb 139 | * replaced all print statements to Log.*() calls 140 | * 141 | * Revision 1.2 2003/05/19 21:56:29 justb 142 | * various fixes for applet clients 143 | * 144 | * Revision 1.1 2003/05/18 16:12:28 justb 145 | * adding support for XML encoded Events 146 | */ 147 | -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/servlet/Pushlet.java: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ushelp/Pushlet-UserUnicast/b423f8e1cde4ae761d16cf55d732a16d5d540d1a/src/nl/justobjects/pushlet/servlet/Pushlet.java -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/test/PushletApplet.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2000 Just Objects B.V. 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | 4 | package nl.justobjects.pushlet.test; 5 | 6 | import nl.justobjects.pushlet.client.PushletClient; 7 | import nl.justobjects.pushlet.client.PushletClientListener; 8 | import nl.justobjects.pushlet.core.Event; 9 | import nl.justobjects.pushlet.core.Protocol; 10 | import nl.justobjects.pushlet.util.PushletException; 11 | 12 | import java.applet.Applet; 13 | import java.awt.*; 14 | 15 | /** 16 | * Tester for applet clients; displays incoming events in text area. 17 | * 18 | * @version $Id: PushletApplet.java,v 1.16 2005/02/18 09:54:15 justb Exp $ 19 | * @author Just van den Broecke - Just Objects © 20 | **/ 21 | public class PushletApplet extends Applet implements PushletClientListener, Protocol { 22 | private TextArea textArea; 23 | private String host = "localhost"; 24 | private int port = 8080; 25 | private String subject; 26 | private PushletClient pushletClient; 27 | private String VERSION = "15.feb.05 #5"; 28 | private String PUSH_MODE = Protocol.MODE_PULL; 29 | 30 | /** One-time setup. */ 31 | public void init() { 32 | // Subject to subscribe to 33 | subject = getParameter(P_SUBJECT); 34 | 35 | host = getDocumentBase().getHost(); 36 | port = getDocumentBase().getPort(); 37 | 38 | // Hmm sometimes this value is -1...(Mozilla with Java 1.3.0 on Win) 39 | if (port == -1) { 40 | port = 80; 41 | } 42 | 43 | setLayout(new GridLayout(1, 1)); 44 | textArea = new TextArea(15, 40); 45 | textArea.setForeground(Color.yellow); 46 | textArea.setBackground(Color.gray); 47 | textArea.setEditable(false); 48 | add(textArea); 49 | p("PushletApplet - " + VERSION); 50 | } 51 | 52 | public void start() { 53 | dbg("start()"); 54 | bailout(); 55 | 56 | try { 57 | pushletClient = new PushletClient(host, port); 58 | p("Created PushletClient"); 59 | 60 | pushletClient.join(); 61 | p("Joined server"); 62 | 63 | pushletClient.listen(this, PUSH_MODE); 64 | p("Listening in mode=" + PUSH_MODE); 65 | 66 | pushletClient.subscribe(subject); 67 | p("Subscribed to=" + subject); 68 | } catch (PushletException pe) { 69 | p("Error exception=" + pe); 70 | bailout(); 71 | } 72 | } 73 | 74 | public void stop() { 75 | dbg("stop()"); 76 | bailout(); 77 | } 78 | 79 | /** Abort event from server. */ 80 | public void onAbort(Event theEvent) { 81 | p(theEvent.toXML()); 82 | bailout(); 83 | } 84 | 85 | /** Data event from server. */ 86 | public void onData(Event theEvent) { 87 | p(theEvent.toXML()); 88 | } 89 | 90 | /** Heartbeat event from server. */ 91 | public void onHeartbeat(Event theEvent) { 92 | p(theEvent.toXML()); 93 | } 94 | 95 | /** Error occurred. */ 96 | public void onError(String message) { 97 | p(message); 98 | bailout(); 99 | } 100 | 101 | private void bailout() { 102 | if (pushletClient != null) { 103 | p("Stopping PushletClient"); 104 | try { 105 | pushletClient.leave(); 106 | } catch (PushletException ignore) { 107 | p("Error during leave pe=" + ignore); 108 | 109 | } 110 | pushletClient = null; 111 | } 112 | } 113 | 114 | /** Generic print. */ 115 | private void p(String s) { 116 | dbg("event: " + s); 117 | synchronized (textArea) { 118 | textArea.append(s + "\n"); 119 | } 120 | } 121 | 122 | /** Generic print. */ 123 | private void dbg(String s) { 124 | System.out.println("[PushletApplet] " + s); 125 | } 126 | } 127 | 128 | /* 129 | * $Log: PushletApplet.java,v $ 130 | * Revision 1.16 2005/02/18 09:54:15 justb 131 | * refactor: rename Publisher Dispatcher and single Subscriber class 132 | * 133 | * Revision 1.15 2005/02/15 15:46:36 justb 134 | * client API improves 135 | * 136 | * Revision 1.14 2005/02/15 13:28:33 justb 137 | * use new PushletClient lib 138 | * 139 | * Revision 1.13 2004/10/25 21:22:26 justb 140 | * *** empty log message *** 141 | * 142 | * Revision 1.12 2004/10/24 13:52:52 justb 143 | * small fixes in client lib 144 | * 145 | * Revision 1.11 2004/10/24 12:58:19 justb 146 | * revised client and test classes for new protocol 147 | * 148 | * Revision 1.10 2004/09/03 22:35:37 justb 149 | * Almost complete rewrite, just checking in now 150 | * 151 | * Revision 1.9 2004/08/12 13:18:54 justb 152 | * cosmetic changes 153 | * 154 | * Revision 1.8 2003/08/17 21:06:37 justb 155 | * version info change 156 | * 157 | * Revision 1.7 2003/08/15 08:37:41 justb 158 | * fix/add Copyright+LGPL file headers and footers 159 | * 160 | * Revision 1.6 2003/08/14 21:43:10 justb 161 | * improved Java client lifecycle; notably stopping listener thread 162 | * 163 | * Revision 1.5 2003/08/13 14:00:00 justb 164 | * some testing for applets; no real change 165 | * 166 | * Revision 1.4 2003/05/19 22:53:33 justb 167 | * more fixes for applets 168 | * 169 | * Revision 1.3 2003/05/19 21:56:29 justb 170 | * various fixes for applet clients 171 | * 172 | * Revision 1.2 2003/05/18 16:15:08 justb 173 | * support for XML encoded Events 174 | * 175 | * Revision 1.1.1.1 2002/09/24 21:02:33 justb 176 | * import to sourceforge 177 | * 178 | */ 179 | -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/test/PushletPingApplication.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2000 Just Objects B.V. 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | 4 | package nl.justobjects.pushlet.test; 5 | 6 | import nl.justobjects.pushlet.client.PushletClient; 7 | import nl.justobjects.pushlet.client.PushletClientListener; 8 | import nl.justobjects.pushlet.core.Event; 9 | import nl.justobjects.pushlet.core.Protocol; 10 | import nl.justobjects.pushlet.util.PushletException; 11 | 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | /** 16 | * Tester to demonstrate Pushlet use in Java applications. 17 | * 18 | * This program does two things: 19 | * (1) it subscribes to the subject "test/ping" 20 | * (2) it publishes an Event with subject "/test/ping" every few seconds. 21 | * 22 | * @version $Id: PushletPingApplication.java,v 1.15 2005/02/21 16:59:17 justb Exp $ 23 | * @author Just van den Broecke - Just Objects © 24 | **/ 25 | public class PushletPingApplication extends Thread implements PushletClientListener, Protocol { 26 | private PushletClient pushletClient; 27 | private String host; 28 | private int port; 29 | private static final String SUBJECT = "/test/ping"; 30 | private static final long PUBLISH_INTERVAL_MILLIS = 3000; 31 | 32 | public PushletPingApplication(String aHost, int aPort) { 33 | host = aHost; 34 | port = aPort; 35 | } 36 | 37 | public void run() { 38 | // Create and start a Pushlet client; we receive callbacks 39 | // through onHeartbeat() and onData(). 40 | try { 41 | pushletClient = new PushletClient(host, port); 42 | pushletClient.setDebug(true); 43 | pushletClient.join(); 44 | pushletClient.listen(this, Protocol.MODE_STREAM); 45 | 46 | // Test subscribe/unsubscribe 47 | String subscriptionId = pushletClient.subscribe(SUBJECT); 48 | pushletClient.unsubscribe(subscriptionId); 49 | 50 | // The real subscribe 51 | pushletClient.subscribe(SUBJECT); 52 | p("pushletClient started"); 53 | } catch (PushletException pe) { 54 | p("Error in setting up pushlet session pe=" + pe); 55 | return; 56 | } 57 | 58 | // Publish an event to the server every N seconds. 59 | Map eventData = new HashMap(2); 60 | int seqNr = 1; 61 | while (true) { 62 | try { 63 | // Create event data 64 | eventData.put("seqNr", "" + seqNr++); 65 | eventData.put("time", "" + System.currentTimeMillis()); 66 | 67 | // POST event to pushlet server 68 | pushletClient.publish(SUBJECT, eventData); 69 | 70 | p("published ping # " + (seqNr - 1) + " - sleeping..."); 71 | Thread.sleep(PUBLISH_INTERVAL_MILLIS); 72 | } catch (Exception e) { 73 | p("Postlet exception: " + e); 74 | System.exit(-1); 75 | } 76 | } 77 | } 78 | 79 | /** Error occurred. */ 80 | public void onError(String message) { 81 | p(message); 82 | } 83 | 84 | /** Abort event from server. */ 85 | public void onAbort(Event theEvent) { 86 | p("onAbort received: " + theEvent); 87 | } 88 | 89 | /** Data event from server. */ 90 | public void onData(Event theEvent) { 91 | // Calculate round trip delay 92 | long then = Long.parseLong(theEvent.getField("time")); 93 | long delay = System.currentTimeMillis() - then; 94 | p("onData: ping #" + theEvent.getField("seqNr") + " in " + delay + " ms"); 95 | } 96 | 97 | /** Heartbeat event from server. */ 98 | public void onHeartbeat(Event theEvent) { 99 | p("onHeartbeat received: " + theEvent); 100 | } 101 | 102 | /** Generic print. */ 103 | public void p(String s) { 104 | System.out.println("[PushletPing] " + s); 105 | } 106 | 107 | /** Main program. */ 108 | public static void main(String args[]) { 109 | for (int i = 0; i < 1; i++) { 110 | if (args.length == 0) { 111 | new PushletPingApplication("localhost", 8080).start(); 112 | } else { 113 | // Supply a host and port 114 | new PushletPingApplication(args[0], Integer.parseInt(args[1])).start(); 115 | } 116 | } 117 | 118 | } 119 | } 120 | 121 | 122 | /* 123 | * $Log: PushletPingApplication.java,v $ 124 | * Revision 1.15 2005/02/21 16:59:17 justb 125 | * SessionManager and session lease introduced 126 | * 127 | * Revision 1.14 2005/02/21 11:50:48 justb 128 | * ohase1 of refactoring Subscriber into Session/Controller/Subscriber 129 | * 130 | * Revision 1.13 2005/02/20 13:05:33 justb 131 | * removed the Postlet (integrated in Pushlet protocol) 132 | * 133 | * Revision 1.12 2005/02/18 09:54:15 justb 134 | * refactor: rename Publisher Dispatcher and single Subscriber class 135 | * 136 | * Revision 1.11 2005/02/15 15:46:37 justb 137 | * client API improves 138 | * 139 | * Revision 1.10 2005/02/15 13:28:08 justb 140 | * use new protocol lib and publish with PushletClient 141 | * 142 | * Revision 1.9 2004/10/24 13:52:52 justb 143 | * small fixes in client lib 144 | * 145 | * Revision 1.8 2004/10/24 12:58:19 justb 146 | * revised client and test classes for new protocol 147 | * 148 | * Revision 1.7 2004/09/03 22:35:37 justb 149 | * Almost complete rewrite, just checking in now 150 | * 151 | * Revision 1.6 2004/08/12 13:18:54 justb 152 | * cosmetic changes 153 | * 154 | * Revision 1.5 2004/03/10 14:53:10 justb 155 | * new 156 | * 157 | * Revision 1.4 2003/08/17 20:30:20 justb 158 | * cosmetic changes 159 | * 160 | * Revision 1.3 2003/08/15 08:37:41 justb 161 | * fix/add Copyright+LGPL file headers and footers 162 | * 163 | * Revision 1.2 2003/05/18 16:15:08 justb 164 | * support for XML encoded Events 165 | * 166 | * Revision 1.1.1.1 2002/09/24 21:02:33 justb 167 | * import to sourceforge 168 | * 169 | * Revision 1.1.1.1 2002/09/20 22:48:19 justb 170 | * import to SF 171 | * 172 | * Revision 1.1.1.1 2002/09/20 14:19:02 justb 173 | * first import into SF 174 | * 175 | * Revision 1.3 2000/08/31 12:49:50 just 176 | * added CVS comment tags for log and copyright 177 | * 178 | * 179 | */ 180 | -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/test/SimpleListener.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2000 Just Objects B.V. 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | 4 | package nl.justobjects.pushlet.test; 5 | 6 | import nl.justobjects.pushlet.client.PushletClient; 7 | import nl.justobjects.pushlet.client.PushletClientListener; 8 | import nl.justobjects.pushlet.core.Event; 9 | import nl.justobjects.pushlet.core.Protocol; 10 | import nl.justobjects.pushlet.util.PushletException; 11 | 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | /** 16 | * Demonstrates join-listen service. 17 | * 18 | * The pushlet protocol supports the "join-listen" service 19 | * which allows stateless (e.g. RESTful) clients to join/subscribe/listen 20 | * using a single HTTP request. 21 | * 22 | * @version $Id: SimpleListener.java,v 1.3 2005/03/14 14:07:23 justb Exp $ 23 | * @author Just van den Broecke - Just Objects © 24 | **/ 25 | public class SimpleListener implements PushletClientListener, Protocol { 26 | private static String SUBJECT = "/temperature"; 27 | private static final String MODE = MODE_STREAM; 28 | 29 | public SimpleListener(String aHost, int aPort) { 30 | // Create and start a Pushlet client; we receive callbacks 31 | // through onHeartbeat() and onData(). 32 | try { 33 | PushletClient pushletClient = new PushletClient(aHost, aPort); 34 | pushletClient.setDebug(false); 35 | pushletClient.join(); 36 | pushletClient.listen(this, MODE, SUBJECT); 37 | p("pushletClient started"); 38 | } catch (PushletException pe) { 39 | p("Error in setting up pushlet session pe=" + pe); 40 | } 41 | } 42 | 43 | /** Error occurred. */ 44 | public void onError(String message) { 45 | p(message); 46 | } 47 | 48 | /** Abort event from server. */ 49 | public void onAbort(Event theEvent) { 50 | p("onAbort received: " + theEvent); 51 | } 52 | 53 | /** Data event from server. */ 54 | public void onData(Event theEvent) { 55 | // Calculate round trip delay 56 | System.out.println(theEvent.toXML()); 57 | } 58 | 59 | /** Heartbeat event from server. */ 60 | public void onHeartbeat(Event theEvent) { 61 | p("onHeartbeat received: " + theEvent); 62 | } 63 | 64 | /** Generic print. */ 65 | public void p(String s) { 66 | System.out.println("[SimpleListener] " + s); 67 | } 68 | 69 | /** Main program. */ 70 | public static void main(String args[]) { 71 | if (args.length == 0) { 72 | new SimpleListener("localhost", 8080); 73 | } 74 | else if (args.length == 1) { 75 | SUBJECT = args[0]; 76 | new SimpleListener("localhost", 8080); 77 | } else { 78 | SUBJECT = args[0]; 79 | // args[1] and [2] should be host and port 80 | new SimpleListener(args[1], Integer.parseInt(args[2])); 81 | } 82 | } 83 | } 84 | 85 | 86 | /* 87 | * $Log: SimpleListener.java,v $ 88 | * Revision 1.3 2005/03/14 14:07:23 justb 89 | * addded subject arg 90 | * 91 | * Revision 1.2 2005/02/28 21:21:32 justb 92 | * no chg 93 | * 94 | * Revision 1.1 2005/02/28 15:58:05 justb 95 | * added SimpleListener example 96 | * 97 | * 98 | */ 99 | -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/test/StressTester.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2000 Just Objects B.V. 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | 4 | package nl.justobjects.pushlet.test; 5 | 6 | import nl.justobjects.pushlet.client.PushletClient; 7 | import nl.justobjects.pushlet.client.PushletClientListener; 8 | import nl.justobjects.pushlet.core.Event; 9 | import nl.justobjects.pushlet.core.Protocol; 10 | import nl.justobjects.pushlet.util.PushletException; 11 | import nl.justobjects.pushlet.util.Rand; 12 | 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | 16 | /** 17 | * Tester to demonstrate Pushlet use in Java applications. 18 | *

19 | * This program does two things: 20 | * (1) it subscribes to the subject "test/ping" 21 | * (2) it publishes an Event with subject "/test/ping" every few seconds. 22 | * 23 | * @author Just van den Broecke - Just Objects © 24 | * @version $Id: StressTester.java,v 1.2 2007/11/09 13:16:57 justb Exp $ 25 | */ 26 | public class StressTester implements Protocol { 27 | static private String host = "localhost"; 28 | static private int port = 8080; 29 | static private int TESTER_COUNT = 10; 30 | private static final String SUBJECT = "/test/ping"; 31 | private static final long MIN_PUBLISH_INTERVAL_MILLIS = 200; 32 | private static final long MAX_PUBLISH_INTERVAL_MILLIS = 1000; 33 | private static final long MIN_SUBSCRIBER_INTERVAL_MILLIS = 500; 34 | private static final long MAX_SUBSCRIBER_INTERVAL_MILLIS = 1000; 35 | 36 | public StressTester() { 37 | } 38 | 39 | public void run() { 40 | new EventPublisher().start(); 41 | new EventSubscriber().start(); 42 | } 43 | 44 | /** 45 | * Generic print. 46 | */ 47 | public void err(String s) { 48 | System.out.println("[StressTester] ERROR" + s); 49 | } 50 | 51 | /** 52 | * Generic print. 53 | */ 54 | public void p(String s) { 55 | System.out.println("[StressTester] " + s); 56 | } 57 | 58 | private class EventSubscriber extends Thread implements PushletClientListener { 59 | private PushletClient pushletClient; 60 | 61 | public void run() { 62 | while (true) { 63 | // Create and start a Pushlet client; we receive callbacks 64 | // through onHeartbeat() and onData(). 65 | try { 66 | pushletClient = new PushletClient(host, port); 67 | // pushletClient.setDebug(true); 68 | pushletClient.join(); 69 | pushletClient.listen(this, Protocol.MODE_STREAM); 70 | //p("listening"); 71 | // Test subscribe/unsubscribe 72 | String subscriptionId = pushletClient.subscribe(SUBJECT); 73 | pushletClient.unsubscribe(subscriptionId); 74 | 75 | // The real subscribe 76 | subscriptionId = pushletClient.subscribe(SUBJECT); 77 | //p("sleeping"); 78 | sleepRandom(); 79 | //p("leaving"); 80 | pushletClient.unsubscribe(subscriptionId); 81 | pushletClient.leave(); 82 | 83 | } catch (Throwable t) { 84 | err("Error in EventSubscriber t=" + t); 85 | return; 86 | } 87 | } 88 | } 89 | 90 | /** 91 | * Error occurred. 92 | */ 93 | public void onError(String message) { 94 | // p(message); 95 | } 96 | 97 | /** 98 | * Abort event from server. 99 | */ 100 | public void onAbort(Event theEvent) { 101 | //p("onAbort received: " + theEvent); 102 | } 103 | 104 | /** 105 | * Data event from server. 106 | */ 107 | public void onData(Event theEvent) { 108 | // Calculate round trip delay 109 | long then = Long.parseLong(theEvent.getField("time")); 110 | long delay = System.currentTimeMillis() - then; 111 | //p("onData: ping #" + theEvent.getField("seqNr") + " in " + delay + " ms"); 112 | } 113 | 114 | /** 115 | * Heartbeat event from server. 116 | */ 117 | public void onHeartbeat(Event theEvent) { 118 | //p("onHeartbeat received: " + theEvent); 119 | } 120 | 121 | private void sleepRandom() throws InterruptedException { 122 | Thread.sleep(Rand.randomLong(MIN_SUBSCRIBER_INTERVAL_MILLIS, MAX_SUBSCRIBER_INTERVAL_MILLIS)); 123 | } 124 | } 125 | 126 | private class EventPublisher extends Thread { 127 | private PushletClient pushletClient; 128 | 129 | public void run() { 130 | // Create and start a Pushlet client; we receive callbacks 131 | // through onHeartbeat() and onData(). 132 | try { 133 | pushletClient = new PushletClient(host, port); 134 | pushletClient.join(); 135 | 136 | // p("pushletClient started"); 137 | } catch (PushletException pe) { 138 | err("Error in EventPublisher pe=" + pe); 139 | return; 140 | } 141 | 142 | // Publish an event to the server every N seconds. 143 | Map eventData = new HashMap(2); 144 | int seqNr = 1; 145 | while (true) { 146 | try { 147 | // Create event data 148 | eventData.put("seqNr", "" + seqNr++); 149 | eventData.put("time", "" + System.currentTimeMillis()); 150 | 151 | // POST event to pushlet server 152 | pushletClient.publish(SUBJECT, eventData); 153 | 154 | Thread.sleep(Rand.randomLong(MIN_PUBLISH_INTERVAL_MILLIS, MAX_PUBLISH_INTERVAL_MILLIS)); 155 | } catch (Exception e) { 156 | p("EventPublisher exception: " + e); 157 | return; 158 | } 159 | } 160 | } 161 | 162 | } 163 | 164 | /** 165 | * Main program. 166 | */ 167 | public static void main(String args[]) { 168 | if (args.length > 0) { 169 | TESTER_COUNT = Integer.parseInt(args[0]); 170 | } 171 | if (args.length == 3) { 172 | host = args[1]; 173 | port = Integer.parseInt(args[2]); 174 | } 175 | 176 | for (int i = 0; i < TESTER_COUNT; i++) { 177 | new StressTester().run(); 178 | } 179 | 180 | } 181 | } 182 | 183 | /* 184 | * $Log: StressTester.java,v $ 185 | * Revision 1.2 2007/11/09 13:16:57 justb 186 | * use Rand from util package (and and Rand.java to pushlet client jar 187 | * 188 | * Revision 1.1 2005/02/28 17:16:58 justb 189 | * simple stress tester 190 | * 191 | * 192 | */ 193 | -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/test/TestEventPullSources.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2000 Just Objects B.V. 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | 4 | package nl.justobjects.pushlet.test; 5 | 6 | import nl.justobjects.pushlet.core.Event; 7 | import nl.justobjects.pushlet.core.EventPullSource; 8 | import nl.justobjects.pushlet.core.SessionManager; 9 | import nl.justobjects.pushlet.util.Rand; 10 | 11 | 12 | /** 13 | * Event sources for testing. 14 | * 15 | * @author Just van den Broecke - Just Objects © 16 | * @version $Id: TestEventPullSources.java,v 1.10 2007/11/09 13:16:57 justb Exp $ 17 | */ 18 | public class TestEventPullSources { 19 | 20 | /** 21 | * Produces a fake temparature event. 22 | */ 23 | static public class TemperatureEventPullSource extends EventPullSource { 24 | String[] cities = {"amsterdam", null, "rotterdam", null, 25 | "leeuwarden", null, "twente", null, "limburg", null}; 26 | 27 | public long getSleepTime() { 28 | return Rand.randomLong(3000, 5000); 29 | } 30 | 31 | public Event pullEvent() { 32 | int cityNumber = Rand.randomInt(0, (cities.length) / 2 - 1); 33 | int nextCityIndex = 2 * cityNumber; 34 | 35 | Event event = Event.createDataEvent("/temperature"); 36 | 37 | event.setField("number", "" + cityNumber); 38 | event.setField("city", cities[nextCityIndex]); 39 | if (cities[nextCityIndex + 1] == null) { 40 | cities[nextCityIndex + 1] = "" + Rand.randomInt(5, 10); 41 | } 42 | int currentCityValue = new Integer(cities[nextCityIndex + 1]).intValue(); 43 | int newCityValue = currentCityValue + Rand.randomInt(-2, 2); 44 | 45 | event.setField("value", "" + newCityValue); 46 | return event; 47 | } 48 | } 49 | 50 | /** 51 | * Produces a ping event. 52 | */ 53 | static public class PingEventPullSource extends EventPullSource { 54 | public long getSleepTime() { 55 | return 3000; 56 | } 57 | 58 | public Event pullEvent() { 59 | 60 | return Event.createDataEvent("/pushlet/ping"); 61 | } 62 | } 63 | 64 | /** 65 | * Produces an event related to the JVM status. 66 | */ 67 | static public class SystemStatusEventPullSource extends EventPullSource { 68 | Runtime runtime = Runtime.getRuntime(); 69 | 70 | public long getSleepTime() { 71 | return 4000; 72 | } 73 | 74 | public Event pullEvent() { 75 | Event event = Event.createDataEvent("/system/jvm"); 76 | event.setField("totalMemory", "" + runtime.totalMemory()); 77 | event.setField("freeMemory", "" + runtime.freeMemory()); 78 | event.setField("maxMemory", "" + runtime.maxMemory()); 79 | int activeCount = Thread.activeCount(); 80 | event.setField("threads", "" + activeCount); 81 | 82 | return event; 83 | } 84 | } 85 | 86 | /** 87 | * Produces an event related to the Dispatcher.getInstance(). status. 88 | */ 89 | static public class PushletStatusEventPullSource extends EventPullSource { 90 | 91 | public long getSleepTime() { 92 | return 5000; 93 | } 94 | 95 | public Event pullEvent() { 96 | Event event = Event.createDataEvent("/system/pushlet"); 97 | // p(Dispatcher.getStatus()); 98 | event.setField("publisher", "" + SessionManager.getInstance().getStatus()); 99 | return event; 100 | } 101 | } 102 | 103 | 104 | /** 105 | * Produces events simulating stocks from the AEX. 106 | */ 107 | static public class AEXStocksEventPullSource extends EventPullSource { 108 | 109 | String[] stocks = {"abn amro", "26", 110 | "aegon", "38", 111 | "ahold", "34", 112 | "akzo nobel", "51", 113 | "asm lith h", "26", 114 | "corus plc", "2", 115 | "dsm", "40", 116 | "elsevier", "14", 117 | "fortis (nl)", "32", 118 | "getronics", "6", 119 | "gucci", "94", 120 | "hagemeyer", "25", 121 | "heineken", "61", 122 | "ing c", "78", 123 | "klm", "66", 124 | "kon olie", "66", 125 | "kpn", "13", 126 | "numico c", "44", 127 | "philips, kon", "38", 128 | "tnt", "26", 129 | "unilever c", "62", 130 | "vendex kbb", "16", 131 | "vnu", "49", 132 | "wolt-kluw c", "25"}; 133 | 134 | public long getSleepTime() { 135 | return Rand.randomLong(2000, 4000); 136 | } 137 | 138 | public Event pullEvent() { 139 | Event event = Event.createDataEvent("/stocks/aex"); 140 | int stockNumber = Rand.randomInt(0, (stocks.length) / 2 - 1); 141 | int nextStockIndex = 2 * stockNumber; 142 | 143 | event.setField("number", "" + stockNumber); 144 | event.setField("name", stocks[nextStockIndex]); 145 | if (stocks[nextStockIndex + 1] == null) { 146 | stocks[nextStockIndex + 1] = "" + Rand.randomInt(50, 150); 147 | } 148 | int currentStockValue = new Integer(stocks[nextStockIndex + 1]).intValue(); 149 | int newStockValue = currentStockValue + Rand.randomInt(-2, 2); 150 | 151 | event.setField("rate", "" + newStockValue + "." + Rand.randomInt(0, 99)); 152 | return event; 153 | } 154 | 155 | } 156 | 157 | /** 158 | * Produces an URL event for automatic webpresentation. 159 | */ 160 | static public class WebPresentationEventPullSource extends EventPullSource { 161 | String slideRootDir = "http://www.justobjects.org/cowcatcher/browse/j2ee/slides/"; 162 | String[] slideURLs = { 163 | "ejb/j2ee/ejbover/slide.0.0.html", 164 | "ejb/j2ee/ejbover/slide.0.1.html", 165 | "ejb/j2ee/ejbover/slide.0.2.html", 166 | "ejb/j2ee/ejbover/slide.0.3.html", 167 | "ejb/j2ee/ejbover/slide.0.4.html" 168 | }; 169 | 170 | int nextSlideNumber = 0; 171 | 172 | public long getSleepTime() { 173 | return 5000; 174 | } 175 | 176 | public Event pullEvent() { 177 | Event event = Event.createDataEvent("/webpres/auto"); 178 | event.setField("url", slideRootDir + slideURLs[nextSlideNumber++]); 179 | if (nextSlideNumber == slideURLs.length) { 180 | nextSlideNumber = 0; 181 | } 182 | // Log.debug("Sending next slide url=" + event.getField("url")); 183 | return event; 184 | } 185 | } 186 | 187 | /** 188 | * Produces an event related to the Dispatcher.getInstance(). status. 189 | */ 190 | static public class TestEventPullSource extends EventPullSource { 191 | private int number = 0; 192 | 193 | public long getSleepTime() { 194 | return 2000; 195 | } 196 | 197 | public Event pullEvent() { 198 | Event event = Event.createDataEvent("/system/test"); 199 | // p(Dispatcher.getInstance()..getStatus()); 200 | event.setField("nr", "" + (number++)); 201 | event.setField("time", "" + System.currentTimeMillis()); 202 | return event; 203 | } 204 | 205 | } 206 | 207 | /** 208 | * Util: stderr print method. 209 | */ 210 | public static void e(String s) { 211 | System.out.println(s); 212 | } 213 | 214 | /** 215 | * Util: stdout print method. 216 | */ 217 | public static void p(String s) { 218 | // System.out.println(s); 219 | } 220 | } 221 | 222 | /* 223 | * $Log: TestEventPullSources.java,v $ 224 | * Revision 1.10 2007/11/09 13:16:57 justb 225 | * use Rand from util package (and and Rand.java to pushlet client jar 226 | * 227 | * Revision 1.9 2005/02/28 09:14:56 justb 228 | * sessmgr/dispatcher factory/singleton support 229 | * 230 | * Revision 1.8 2005/02/21 16:59:17 justb 231 | * SessionManager and session lease introduced 232 | * 233 | * Revision 1.7 2005/02/18 10:07:23 justb 234 | * many renamings of classes (make names compact) 235 | * 236 | * Revision 1.6 2005/02/18 09:54:15 justb 237 | * refactor: rename Publisher Dispatcher.getInstance(). and single Subscriber class 238 | * 239 | * Revision 1.5 2004/09/03 22:35:37 justb 240 | * Almost complete rewrite, just checking in now 241 | * 242 | * Revision 1.4 2003/12/03 21:16:58 justb 243 | * *** empty log message *** 244 | * 245 | * Revision 1.3 2003/08/15 08:37:41 justb 246 | * fix/add Copyright+LGPL file headers and footers 247 | * 248 | * Revision 1.2 2003/05/18 16:15:08 justb 249 | * support for XML encoded Events 250 | * 251 | * Revision 1.1.1.1 2002/09/24 21:02:33 justb 252 | * import to sourceforge 253 | * 254 | * Revision 1.1.1.1 2002/09/20 22:48:19 justb 255 | * import to SF 256 | * 257 | * Revision 1.1.1.1 2002/09/20 14:19:01 justb 258 | * first import into SF 259 | * 260 | * Revision 1.6 2002/07/29 10:17:22 just 261 | * no message 262 | * 263 | * Revision 1.5 2001/02/18 23:45:13 just 264 | * fixes for AEX 265 | * 266 | * Revision 1.4 2000/10/30 14:16:09 just 267 | * no message 268 | * 269 | * Revision 1.3 2000/08/31 12:49:50 just 270 | * added CVS comment tags for log and copyright 271 | * 272 | * 273 | */ 274 | -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/test/TestEventPushSources.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2000 Just Objects B.V. 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | 4 | package nl.justobjects.pushlet.test; 5 | 6 | import nl.justobjects.pushlet.core.Dispatcher; 7 | import nl.justobjects.pushlet.core.Event; 8 | import nl.justobjects.pushlet.core.EventSource; 9 | import nl.justobjects.pushlet.util.Rand; 10 | 11 | import java.io.BufferedReader; 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | import java.io.InputStreamReader; 15 | import java.net.URL; 16 | import java.util.StringTokenizer; 17 | import java.util.Vector; 18 | 19 | /** 20 | * Event sources that push events (for testing). 21 | * 22 | * @author Just van den Broecke - Just Objects © 23 | * @version $Id: TestEventPushSources.java,v 1.10 2007/11/09 13:16:57 justb Exp $ 24 | */ 25 | public class TestEventPushSources { 26 | 27 | static public class AEXStocksEventPushSourceABN { 28 | String pageURL = "http://ri2.rois.com/E36msPtnZC0e15CVb4KT97JAGfGSfCcrvv6*FcyZIoNyh/CTIB/RI2APISNAP?RIC=0%23.AEX&FORMAT=XML"; 29 | // This could be further expanded: getting the Reuters AEX stocks 30 | // as XML from ABN with this URL, but we may get into legal problems... 31 | } 32 | 33 | /** 34 | * Produces events from REAL stocks from the AEX. 35 | */ 36 | static public class AEXStocksEventPushSource implements EventSource, Runnable { 37 | /** 38 | * Here we get our stocks from. 39 | */ 40 | String pageURL = "http://www.debeurs.nl/koersen/aex.asp"; 41 | Thread thread = null; 42 | volatile boolean active = false; 43 | 44 | // Since Baan has been thrown out... 45 | public final static int NR_OF_STOCKS = 24; 46 | 47 | public final static String EMPTY = "wait..."; 48 | private int restarts = 1; 49 | 50 | class Stock { 51 | public String name = EMPTY; 52 | public String rate = EMPTY; 53 | volatile public boolean modified = false; 54 | } 55 | 56 | Vector stocksCache = new Vector(NR_OF_STOCKS); 57 | 58 | public AEXStocksEventPushSource() { 59 | for (int i = 0; i < NR_OF_STOCKS; i++) { 60 | stocksCache.addElement(new Stock()); 61 | } 62 | // updateCache(); 63 | } 64 | 65 | /** 66 | * Activate the event source. 67 | */ 68 | synchronized public void activate() { 69 | e("activating..."); 70 | // Stop a possibly running thread 71 | stopThread(); 72 | 73 | // Start new thread and 74 | thread = new Thread(this, "AEXStocksPublisher-" + (restarts++)); 75 | active = true; 76 | thread.start(); 77 | e("activated"); 78 | } 79 | 80 | /** 81 | * Deactivate the event source. 82 | */ 83 | synchronized public void passivate() { 84 | e("passivating..."); 85 | active = false; 86 | stopThread(); 87 | 88 | // Mark the cache modified so we'll send the contents 89 | // on the next activation. 90 | for (int i = 0; i < NR_OF_STOCKS; i++) { 91 | ((Stock) stocksCache.elementAt(i)).modified = true; 92 | } 93 | 94 | e("passivated"); 95 | } 96 | 97 | 98 | /** 99 | * Deactivate the event source. 100 | */ 101 | synchronized public void stop() { 102 | } 103 | 104 | public void run() { 105 | // Publish cache content (if any) first. 106 | publishStocks(); 107 | 108 | int count = 5; // enforce update first 109 | while (active) { 110 | 111 | // Only do work if active 112 | // Update cache every 10 secs. 113 | if (count == 5) { 114 | updateCache(); 115 | count = 0; 116 | } 117 | count++; 118 | 119 | // Do updates for changed stock rates 120 | sendUpdates(); 121 | 122 | // If we were interrupted just return. 123 | if (thread == null || thread.isInterrupted()) { 124 | break; 125 | } 126 | 127 | // Sleep 2 secs before sending next updates 128 | try { 129 | thread.sleep(2000); 130 | } catch (InterruptedException ie) { 131 | break; 132 | } 133 | } 134 | 135 | // Loop terminated: reset vars 136 | thread = null; 137 | active = false; 138 | } 139 | 140 | private String getStocksLine() { 141 | BufferedReader br = null; 142 | InputStream is = null; 143 | String nextLine = ""; 144 | 145 | // Read line from server 146 | try { 147 | is = new URL(pageURL).openStream(); 148 | br = new BufferedReader(new InputStreamReader(is)); 149 | boolean foundLine = false; 150 | while (!foundLine) { 151 | nextLine = br.readLine(); 152 | if (nextLine == null) { 153 | return ""; 154 | } 155 | foundLine = (nextLine.indexOf("details.asp?iid=14053&parent=aex") != -1); 156 | } 157 | } catch (Exception e) { 158 | e("could not open or read URL pageURL=" + pageURL + " ex=" + e); 159 | return ""; 160 | } finally { 161 | try { 162 | if (is != null) is.close(); 163 | } catch (IOException ignore) { 164 | } 165 | } 166 | return nextLine; 167 | } 168 | 169 | private void publishStocks() { 170 | // Publish only modified stocks from the cache 171 | for (int i = 0; i < NR_OF_STOCKS; i++) { 172 | Stock nextStock = (Stock) stocksCache.elementAt(i); 173 | 174 | // Publish modified stocks 175 | if (nextStock.modified) { 176 | publishStock(i, nextStock.name, nextStock.rate); 177 | nextStock.modified = false; 178 | try { 179 | Thread.sleep(400); 180 | } catch (InterruptedException ie) { 181 | return; 182 | } 183 | } 184 | } 185 | } 186 | 187 | private void publishStock(int index, String name, String rate) { 188 | Event event = Event.createDataEvent("/stocks/aex"); 189 | event.setField("number", index + ""); 190 | event.setField("name", name); 191 | event.setField("rate", rate); 192 | p("publish: nr=" + index + " name=" + name + " rate=" + rate); 193 | Dispatcher.getInstance().multicast(event); 194 | } 195 | 196 | private void sendUpdates() { 197 | p("sending updates"); 198 | // In any case send a random stock value by 199 | // making it modified, just to see something moving... 200 | int randomIndex = Rand.randomInt(0, NR_OF_STOCKS - 1); 201 | Stock randomStock = (Stock) stocksCache.elementAt(randomIndex); 202 | randomStock.modified = true; 203 | 204 | publishStocks(); 205 | } 206 | 207 | private void stopThread() { 208 | if (thread != null) { 209 | thread.interrupt(); 210 | thread = null; 211 | } 212 | } 213 | 214 | private void updateCache() { 215 | p("updating Cache"); 216 | 217 | // Get the line with all stocks from HTML page 218 | String stocksLine = getStocksLine(); 219 | if ("".equals(stocksLine)) { 220 | e("updateCache: stocksLine == null"); 221 | return; 222 | } 223 | 224 | // Parse the stocksline and put in cache. 225 | // Beware: this is the messy part!! 226 | // We assume that stock/names and rates are located at 227 | // regular positions in the line. 228 | String delim = "<>"; 229 | StringTokenizer st = new StringTokenizer(stocksLine, delim); 230 | String nextToken = ""; 231 | int count = 0; 232 | String nextStock = ""; 233 | String nextQuote = ""; 234 | String currentQuote = null; 235 | int index = -1; 236 | while (st.hasMoreTokens()) { 237 | nextToken = st.nextToken(); 238 | count++; 239 | // The with the stock name 240 | if ((count - 5) % 57 == 0) { 241 | p("c=" + count + " s=" + nextToken); 242 | nextStock = nextToken; 243 | } 244 | 245 | // The with the stock rate 246 | if ((count - 10) % 57 == 0) { 247 | nextQuote = nextToken; 248 | index++; 249 | p("c=" + count + " val=" + nextQuote); 250 | Stock currentStock = (Stock) stocksCache.elementAt(index); 251 | 252 | // Only update new or modified stocks 253 | if (EMPTY.equals(currentStock.rate) || !currentStock.rate.equals(nextQuote)) { 254 | p("modified: " + nextStock); 255 | currentStock.name = nextStock; 256 | currentStock.rate = nextQuote; 257 | currentStock.modified = true; 258 | } 259 | } 260 | } 261 | } 262 | } 263 | 264 | 265 | /** 266 | * Util: stderr print method. 267 | */ 268 | public static void e(String s) { 269 | System.out.println("AEXStocksEventPushSource: " + s); 270 | } 271 | 272 | /** 273 | * Util: stdout print method. 274 | */ 275 | public static void p(String s) { 276 | // System.out.println(s); 277 | } 278 | 279 | 280 | public static void main(String[] args) { 281 | // new TestEventPushSources$AEXStocksEventPushSource(); 282 | } 283 | } 284 | 285 | /* 286 | * $Log: TestEventPushSources.java,v $ 287 | * Revision 1.10 2007/11/09 13:16:57 justb 288 | * use Rand from util package (and and Rand.java to pushlet client jar 289 | * 290 | * Revision 1.9 2005/02/28 09:14:56 justb 291 | * sessmgr/dispatcher factory/singleton support 292 | * 293 | * Revision 1.8 2005/02/21 16:59:17 justb 294 | * SessionManager and session lease introduced 295 | * 296 | * Revision 1.7 2005/02/18 10:07:23 justb 297 | * many renamings of classes (make names compact) 298 | * 299 | * Revision 1.6 2005/02/18 09:54:15 justb 300 | * refactor: rename Publisher Dispatcher and single Subscriber class 301 | * 302 | * Revision 1.5 2004/09/03 22:35:37 justb 303 | * Almost complete rewrite, just checking in now 304 | * 305 | * Revision 1.4 2004/03/10 15:45:55 justb 306 | * many cosmetic changes 307 | * 308 | * Revision 1.3 2003/08/15 08:37:41 justb 309 | * fix/add Copyright+LGPL file headers and footers 310 | * 311 | * Revision 1.2 2003/05/18 16:15:08 justb 312 | * support for XML encoded Events 313 | * 314 | * Revision 1.1.1.1 2002/09/24 21:02:33 justb 315 | * import to sourceforge 316 | * 317 | * Revision 1.1.1.1 2002/09/20 22:48:20 justb 318 | * import to SF 319 | * 320 | * Revision 1.1.1.1 2002/09/20 14:19:02 justb 321 | * first import into SF 322 | * 323 | * Revision 1.6 2001/02/18 23:45:13 just 324 | * fixes for AEX 325 | * 326 | * Revision 1.5 2000/10/30 14:16:09 just 327 | * no message 328 | * 329 | * Revision 1.4 2000/09/24 21:02:43 just 330 | * chnages due to changed webpage in debeurs.nl 331 | * 332 | * Revision 1.3 2000/08/31 12:49:50 just 333 | * added CVS comment tags for log and copyright 334 | * 335 | * 336 | */ 337 | -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/util/DefaultLogger.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2000 Just Objects B.V. 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | 4 | package nl.justobjects.pushlet.util; 5 | 6 | /** 7 | * Default logger. 8 | *

9 | * Logs to stdout. Override this class by setting "logger.class" in pushlet.properties to your own logger 10 | * to integrate your own logging library. 11 | * 12 | * @author Just van den Broecke 13 | * @version $Id: DefaultLogger.java,v 1.2 2007/12/07 12:57:40 justb Exp $ 14 | */ 15 | public class DefaultLogger implements PushletLogger { 16 | 17 | 18 | /** 19 | * Level intialized with default. 20 | */ 21 | private int level = LOG_LEVEL_INFO; 22 | 23 | public DefaultLogger() { 24 | } 25 | 26 | public void init() { 27 | 28 | } 29 | 30 | /** 31 | * Log message for trace level. 32 | * 33 | * @param aMessage the message to be logged 34 | */ 35 | public void trace(String aMessage) { 36 | if (level < LOG_LEVEL_TRACE) { 37 | return; 38 | } 39 | print("TRACE", aMessage); 40 | } 41 | 42 | /** 43 | * Log message for debug level. 44 | * 45 | * @param aMessage the message to be logged 46 | */ 47 | public void debug(String aMessage) { 48 | if (level < LOG_LEVEL_DEBUG) { 49 | return; 50 | } 51 | print("DEBUG", aMessage); 52 | } 53 | 54 | /** 55 | * Log message for info level. 56 | * 57 | * @param aMessage the message to be logged 58 | */ 59 | public void info(String aMessage) { 60 | if (level < LOG_LEVEL_INFO) { 61 | return; 62 | } 63 | print("INFO", aMessage); 64 | } 65 | 66 | /** 67 | * Log message for warning level. 68 | * 69 | * @param aMessage the message to be logged 70 | */ 71 | public void warn(String aMessage) { 72 | if (level < LOG_LEVEL_WARN) { 73 | return; 74 | } 75 | print("WARN", aMessage); 76 | } 77 | 78 | /** 79 | * Log message for warning level with exception. 80 | * 81 | * @param aMessage the message to be logged 82 | * @param aThrowable the exception 83 | */ 84 | public void warn(String aMessage, Throwable aThrowable) { 85 | warn(aMessage + " exception=" + aThrowable); 86 | } 87 | 88 | /** 89 | * Log message for error level. 90 | * 91 | * @param aMessage the message to be logged 92 | */ 93 | public void error(String aMessage) { 94 | if (level < LOG_LEVEL_ERROR) { 95 | return; 96 | } 97 | print("FATAL", aMessage); 98 | } 99 | 100 | /** 101 | * Log message (error level with exception). 102 | * 103 | * @param aMessage the message to be logged 104 | * @param aThrowable the exception 105 | */ 106 | public void error(String aMessage, Throwable aThrowable) { 107 | error(aMessage + " exception=" + aThrowable); 108 | } 109 | 110 | /** 111 | * Log message for fatal level. 112 | * 113 | * @param aMessage the message to be logged 114 | */ 115 | public void fatal(String aMessage) { 116 | if (level < LOG_LEVEL_FATAL) { 117 | return; 118 | } 119 | print("FATAL", aMessage); 120 | } 121 | 122 | /** 123 | * Log message (fatal level with exception). 124 | * 125 | * @param aMessage the message to be logged 126 | * @param aThrowable the exception 127 | */ 128 | public void fatal(String aMessage, Throwable aThrowable) { 129 | fatal(aMessage + " exception=" + aThrowable); 130 | } 131 | 132 | /** 133 | * Set log level 134 | * 135 | * @param aLevel the message to be logged 136 | */ 137 | public void setLevel(int aLevel) { 138 | level = aLevel; 139 | } 140 | 141 | /** 142 | * Print message. 143 | * 144 | * @param aTag the log type 145 | * @param aMessage the message to be logged 146 | */ 147 | private void print(String aTag, String aMessage) { 148 | // SImple std out e.g. to catalina.out in Tomcat 149 | System.out.println("Pushlet[" + aTag + "] " + aMessage); 150 | } 151 | 152 | } 153 | 154 | /* 155 | * $Log: DefaultLogger.java,v $ 156 | * Revision 1.2 2007/12/07 12:57:40 justb 157 | * added log4j and make it the default logging method 158 | * 159 | * Revision 1.1 2007/11/23 21:10:17 justb 160 | * add hooks for custom logging (you can override DefaultLogger in pushlet.properties) 161 | * 162 | * 163 | * 164 | */ -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/util/Log.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2000 Just Objects B.V. 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | 4 | package nl.justobjects.pushlet.util; 5 | 6 | import nl.justobjects.pushlet.core.Config; 7 | import nl.justobjects.pushlet.core.ConfigDefs; 8 | 9 | /** 10 | * Logging wrapper. 11 | *

12 | * Provides a hook to direct logging to your own logging library. Override the DefaultLogger class by setting 13 | * "logger.class" in pushlet.properties to your own logger 14 | * to integrate your own logging library. 15 | * 16 | * @author Just van den Broecke 17 | * @version $Id: Log.java,v 1.5 2007/12/07 12:57:40 justb Exp $ 18 | */ 19 | public class Log implements ConfigDefs { 20 | /** 21 | * Init with default to have at least some logging. 22 | */ 23 | private static PushletLogger logger = new DefaultLogger(); 24 | 25 | /** 26 | * General purpose initialization. 27 | */ 28 | static public void init() { 29 | try { 30 | logger = (PushletLogger) Config.getClass(LOGGER_CLASS, "nl.justobjects.pushlet.util.DefaultLogger").newInstance(); 31 | } catch (Throwable t) { 32 | // Hmmm cannot log this since we don't have a log... 33 | System.out.println("Cannot instantiate Logger from config ex=" + t); 34 | return; 35 | } 36 | 37 | logger.init(); 38 | 39 | // Set log level 40 | logger.setLevel(Config.getIntProperty(Config.LOG_LEVEL)); 41 | 42 | logger.info("Logging intialized logger class=" + logger.getClass()); 43 | } 44 | 45 | /** 46 | * Log message for trace level. 47 | * 48 | * @param aMessage the message to be logged 49 | */ 50 | static public void trace(String aMessage) { 51 | logger.debug(aMessage); 52 | } 53 | 54 | /** 55 | * Log message for debug level. 56 | * 57 | * @param aMessage the message to be logged 58 | */ 59 | static public void debug(String aMessage) { 60 | logger.debug(aMessage); 61 | } 62 | 63 | /** 64 | * Log message for info level. 65 | * 66 | * @param aMessage the message to be logged 67 | */ 68 | static public void info(String aMessage) { 69 | logger.info(aMessage); 70 | } 71 | 72 | /** 73 | * Log message for warning level. 74 | * 75 | * @param aMessage the message to be logged 76 | */ 77 | static public void warn(String aMessage) { 78 | logger.warn(aMessage); 79 | } 80 | 81 | /** 82 | * Log message for warning level with exception. 83 | * 84 | * @param aMessage the message to be logged 85 | * @param aThrowable the exception 86 | */ 87 | static public void warn(String aMessage, Throwable aThrowable) { 88 | logger.warn(aMessage, aThrowable); 89 | } 90 | 91 | /** 92 | * Log message for error level. 93 | * 94 | * @param aMessage the message to be logged 95 | */ 96 | static public void error(String aMessage) { 97 | logger.error(aMessage); 98 | } 99 | 100 | /** 101 | * Log message (error level with exception). 102 | * 103 | * @param aMessage the message to be logged 104 | * @param aThrowable the exception 105 | */ 106 | static public void error(String aMessage, Throwable aThrowable) { 107 | logger.error(aMessage, aThrowable); 108 | } 109 | 110 | /** 111 | * Log message for fatal level. 112 | * 113 | * @param aMessage the message to be logged 114 | */ 115 | static public void fatal(String aMessage) { 116 | logger.fatal(aMessage); 117 | } 118 | 119 | /** 120 | * Log message (fatal level with exception). 121 | * 122 | * @param aMessage the message to be logged 123 | * @param aThrowable the exception 124 | */ 125 | static public void fatal(String aMessage, Throwable aThrowable) { 126 | logger.fatal(aMessage, aThrowable); 127 | } 128 | 129 | /** 130 | * Set log level 131 | * 132 | * @param aLevel the message to be logged 133 | */ 134 | static public void setLevel(int aLevel) { 135 | logger.setLevel(aLevel); 136 | } 137 | } 138 | 139 | /* 140 | * $Log: Log.java,v $ 141 | * Revision 1.5 2007/12/07 12:57:40 justb 142 | * added log4j and make it the default logging method 143 | * 144 | * Revision 1.4 2007/11/23 21:29:43 justb 145 | * add hooks for custom logging (you can override DefaultLogger in pushlet.properties) 146 | * 147 | * Revision 1.3 2007/11/23 21:10:17 justb 148 | * add hooks for custom logging (you can override DefaultLogger in pushlet.properties) 149 | * 150 | * Revision 1.2 2005/02/21 11:15:59 justb 151 | * support log levels 152 | * 153 | * Revision 1.1 2005/02/18 10:07:23 justb 154 | * many renamings of classes (make names compact) 155 | * 156 | * Revision 1.7 2004/09/03 22:35:37 justb 157 | * Almost complete rewrite, just checking in now 158 | * 159 | * Revision 1.6 2004/08/12 13:16:08 justb 160 | * make debug flag false 161 | * 162 | * Revision 1.5 2004/03/10 14:01:55 justb 163 | * formatting and *Subscriber refactoring 164 | * 165 | * Revision 1.4 2003/08/15 09:54:46 justb 166 | * fix javadoc warnings 167 | * 168 | * Revision 1.3 2003/08/15 08:37:40 justb 169 | * fix/add Copyright+LGPL file headers and footers 170 | * 171 | * Revision 1.2 2003/08/12 09:42:47 justb 172 | * enhancements 173 | * 174 | * Revision 1.1 2003/08/12 08:46:00 justb 175 | * cvs comment tags added 176 | * 177 | * 178 | */ -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/util/Log4jLogger.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2000 Just Objects B.V. 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | 4 | package nl.justobjects.pushlet.util; 5 | 6 | import org.apache.log4j.Level; 7 | import org.apache.log4j.LogManager; 8 | import org.apache.log4j.Logger; 9 | 10 | /** 11 | * Logger to use Log4j for logging. 12 | *

13 | * Logs using Log4j. 14 | * This class will require a log4j library in the classpath of the Pushlet. 15 | * 16 | * @author Uli Romahn 17 | * @version $Id: Log4jLogger.java,v 1.1 2007/12/07 12:57:40 justb Exp $ 18 | */ 19 | public class Log4jLogger implements PushletLogger { 20 | 21 | /** 22 | * Level intialized with default. 23 | */ 24 | private Logger logger = LogManager.getLogger("pushlet"); 25 | 26 | 27 | /* (non-Javadoc) 28 | * @see nl.justobjects.pushlet.util.PushletLogger#init() 29 | */ 30 | public void init() { 31 | setLevel(LOG_LEVEL_INFO); 32 | } 33 | 34 | /* (non-Javadoc) 35 | * @see nl.justobjects.pushlet.util.PushletLogger#debug(java.lang.String) 36 | */ 37 | public void debug(String aMessage) { 38 | if (!logger.isDebugEnabled()) { 39 | return; 40 | } 41 | logger.debug(aMessage); 42 | } 43 | 44 | /* (non-Javadoc) 45 | * @see nl.justobjects.pushlet.util.PushletLogger#error(java.lang.String) 46 | */ 47 | public void error(String aMessage) { 48 | logger.error(aMessage); 49 | } 50 | 51 | /* (non-Javadoc) 52 | * @see nl.justobjects.pushlet.util.PushletLogger#error(java.lang.String, java.lang.Throwable) 53 | */ 54 | public void error(String aMessage, Throwable aThrowable) { 55 | logger.error(aMessage, aThrowable); 56 | } 57 | 58 | /* (non-Javadoc) 59 | * @see nl.justobjects.pushlet.util.PushletLogger#fatal(java.lang.String) 60 | */ 61 | public void fatal(String aMessage) { 62 | logger.fatal(aMessage); 63 | } 64 | 65 | /* (non-Javadoc) 66 | * @see nl.justobjects.pushlet.util.PushletLogger#fatal(java.lang.String, java.lang.Throwable) 67 | */ 68 | public void fatal(String aMessage, Throwable aThrowable) { 69 | logger.fatal(aMessage, aThrowable); 70 | } 71 | 72 | /* (non-Javadoc) 73 | * @see nl.justobjects.pushlet.util.PushletLogger#info(java.lang.String) 74 | */ 75 | public void info(String aMessage) { 76 | if (!logger.isInfoEnabled()) { 77 | return; 78 | } 79 | logger.info(aMessage); 80 | } 81 | 82 | /* (non-Javadoc) 83 | * @see nl.justobjects.pushlet.util.PushletLogger#trace(java.lang.String) 84 | */ 85 | public void trace(String aMessage) { 86 | logger.trace(aMessage); 87 | } 88 | 89 | /* (non-Javadoc) 90 | * @see nl.justobjects.pushlet.util.PushletLogger#warn(java.lang.String) 91 | */ 92 | public void warn(String aMessage) { 93 | logger.warn(aMessage); 94 | } 95 | 96 | /* (non-Javadoc) 97 | * @see nl.justobjects.pushlet.util.PushletLogger#warn(java.lang.String, java.lang.Throwable) 98 | */ 99 | public void warn(String aMessage, Throwable aThrowable) { 100 | logger.warn(aMessage, aThrowable); 101 | } 102 | 103 | /* (non-Javadoc) 104 | * @see nl.justobjects.pushlet.util.PushletLogger#setLevel(int) 105 | */ 106 | public void setLevel(int aLevel) { 107 | if (aLevel < LOG_LEVEL_FATAL) { 108 | logger.setLevel(Level.OFF); 109 | } else { 110 | switch (aLevel) { 111 | case LOG_LEVEL_FATAL: 112 | logger.setLevel(Level.FATAL); 113 | break; 114 | case LOG_LEVEL_ERROR: 115 | logger.setLevel(Level.ERROR); 116 | break; 117 | case LOG_LEVEL_WARN: 118 | logger.setLevel(Level.WARN); 119 | break; 120 | case LOG_LEVEL_INFO: 121 | logger.setLevel(Level.INFO); 122 | break; 123 | case LOG_LEVEL_DEBUG: 124 | logger.setLevel(Level.DEBUG); 125 | break; 126 | case LOG_LEVEL_TRACE: 127 | logger.setLevel(Level.TRACE); 128 | break; 129 | default: 130 | logger.setLevel(Level.INFO); 131 | } 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/util/PushletException.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2000 Just Objects B.V. 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | 4 | package nl.justobjects.pushlet.util; 5 | 6 | /** 7 | * Generic exception wrapper. 8 | * 9 | * @author Just van den Broecke 10 | * @version $Id: PushletException.java,v 1.1 2005/02/15 15:14:34 justb Exp $ 11 | */ 12 | public class PushletException extends Exception { 13 | 14 | private PushletException() { 15 | } 16 | 17 | public PushletException(String aMessage, Throwable t) { 18 | super(aMessage + "\n embedded exception=" + t.toString()); 19 | } 20 | 21 | public PushletException(String aMessage) { 22 | super(aMessage); 23 | } 24 | 25 | public PushletException(Throwable t) { 26 | this("PushletException: ", t); 27 | } 28 | 29 | public String toString() { 30 | return "PushletException: " + getMessage(); 31 | } 32 | } 33 | 34 | /* 35 | * $Log: PushletException.java,v $ 36 | * Revision 1.1 2005/02/15 15:14:34 justb 37 | * *** empty log message *** 38 | * 39 | 40 | * 41 | */ -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/util/PushletLogger.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2000 Just Objects B.V. 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | 4 | package nl.justobjects.pushlet.util; 5 | 6 | import nl.justobjects.pushlet.core.ConfigDefs; 7 | 8 | /** 9 | * Logger interface to allow different logging providers. 10 | *

11 | * 12 | * @author Ulrich Romahn 13 | * @version $Id: PushletLogger.java,v 1.1 2007/12/07 12:57:40 justb Exp $ 14 | */ 15 | public interface PushletLogger extends ConfigDefs { 16 | 17 | /** 18 | * Method allowing to initialize our logger 19 | */ 20 | public void init(); 21 | 22 | /** 23 | * Log message for trace level. 24 | * 25 | * @param aMessage the message to be logged 26 | */ 27 | public void trace(String aMessage); 28 | 29 | /** 30 | * Log message for debug level. 31 | * 32 | * @param aMessage the message to be logged 33 | */ 34 | public void debug(String aMessage); 35 | 36 | /** 37 | * Log message for info level. 38 | * 39 | * @param aMessage the message to be logged 40 | */ 41 | public void info(String aMessage); 42 | 43 | /** 44 | * Log message for warning level. 45 | * 46 | * @param aMessage the message to be logged 47 | */ 48 | public void warn(String aMessage); 49 | 50 | /** 51 | * Log message for warning level with exception. 52 | * 53 | * @param aMessage the message to be logged 54 | * @param aThrowable the exception 55 | */ 56 | public void warn(String aMessage, Throwable aThrowable); 57 | 58 | /** 59 | * Log message for error level. 60 | * 61 | * @param aMessage the message to be logged 62 | */ 63 | public void error(String aMessage); 64 | 65 | /** 66 | * Log message (error level with exception). 67 | * 68 | * @param aMessage the message to be logged 69 | * @param aThrowable the exception 70 | */ 71 | public void error(String aMessage, Throwable aThrowable); 72 | 73 | /** 74 | * Log message for fatal level. 75 | * 76 | * @param aMessage the message to be logged 77 | */ 78 | public void fatal(String aMessage); 79 | 80 | /** 81 | * Log message (fatal level with exception). 82 | * 83 | * @param aMessage the message to be logged 84 | * @param aThrowable the exception 85 | */ 86 | public void fatal(String aMessage, Throwable aThrowable); 87 | 88 | /** 89 | * Set log level 90 | * 91 | * @param aLevel a valid Level from ConfigDefs 92 | */ 93 | public void setLevel(int aLevel); 94 | } 95 | -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/util/Rand.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2000 Just Objects B.V. 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | package nl.justobjects.pushlet.util; 4 | 5 | import java.io.File; 6 | import java.io.FileOutputStream; 7 | import java.io.PrintWriter; 8 | import java.util.Random; 9 | 10 | /** 11 | * Randomizing routines. 12 | * 13 | * @author Just van den Broecke 14 | * @version $Id: Rand.java,v 1.4 2007/12/07 12:57:40 justb Exp $ 15 | */ 16 | public class Rand { 17 | private static char CHARS[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'r', 's', 't', 'u', 'v', 'w', 'y', 'z'}; 18 | private static char NON_VOWELS[] = {'b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'z'}; 19 | private static char VOWELS[] = {'a', 'e', 'i', 'o', 'u', 'y'}; 20 | private static Random random = new Random(); 21 | 22 | public static char randomChar() { 23 | return CHARS[randomInt(0, CHARS.length - 1)]; 24 | } 25 | 26 | public static char randomVowel() { 27 | return VOWELS[randomInt(0, VOWELS.length - 1)]; 28 | } 29 | 30 | public static char randomNonVowel() { 31 | return NON_VOWELS[randomInt(0, NON_VOWELS.length - 1)]; 32 | } 33 | 34 | public static File randomTempDir() throws Exception { 35 | File file = new File(System.getProperty("java.io.tmpdir") + File.separator + "oasetest" + File.separator + randomString(12)); 36 | file.mkdirs(); 37 | file.deleteOnExit(); 38 | return file; 39 | } 40 | 41 | public static File randomTempFile() throws Exception { 42 | File file = new File(System.getProperty("java.io.tmpdir") + File.separator + "oase-" + randomString(6)); 43 | file.createNewFile(); 44 | file.deleteOnExit(); 45 | return file; 46 | } 47 | 48 | public static File randomBinaryFile(int aSize) throws Exception { 49 | File file = randomTempFile(); 50 | FileOutputStream fos = new FileOutputStream(file); 51 | fos.write(randomBytes(aSize)); 52 | fos.close(); 53 | return file; 54 | } 55 | 56 | public static File randomTextFile(int aSize) throws Exception { 57 | File file = randomTempFile(); 58 | PrintWriter pw = new PrintWriter(new FileOutputStream(file)); 59 | pw.write(randomString(aSize)); 60 | pw.close(); 61 | return file; 62 | } 63 | 64 | public static byte[] randomBytes(int aSize) { 65 | return randomBlob(aSize); 66 | } 67 | 68 | public static byte[] randomBlob(int aSize) { 69 | byte[] retval = new byte[aSize]; 70 | for (int i = 0; i < retval.length; i++) { 71 | retval[i] = randomByte(); 72 | } 73 | return retval; 74 | } 75 | 76 | public static byte randomByte() { 77 | return (byte) random.nextInt(); 78 | } 79 | 80 | public static double randomDouble() { 81 | return random.nextLong(); 82 | } 83 | 84 | 85 | public static int randomInt() { 86 | return random.nextInt(); 87 | } 88 | 89 | public static int randomInt(int min, int max) { 90 | return (int) ((Math.random() * (double) (max + 1 - min)) + min); 91 | } 92 | 93 | public static long randomLong() { 94 | return random.nextLong(); 95 | } 96 | 97 | public static long randomLong(long min, long max) { 98 | return (long) ((Math.random() * (double) (max + 1L - min)) + min); 99 | } 100 | 101 | public static String randomName(int aLength) { 102 | StringBuffer sb = new StringBuffer(aLength); 103 | for (int i = 0; i < aLength; i++) { 104 | sb.append(i % 2 == 0 ? randomNonVowel() : randomVowel()); 105 | } 106 | return sb.toString(); 107 | } 108 | 109 | public static void randomSleep(long min, long max) { 110 | try { 111 | Thread.sleep(randomLong(min, max)); 112 | } catch (InterruptedException ie) { 113 | 114 | } 115 | } 116 | 117 | public static String randomString(int aLength) { 118 | StringBuffer sb = new StringBuffer(aLength); 119 | for (int i = 0; i < aLength; i++) { 120 | sb.append(randomChar()); 121 | } 122 | return sb.toString(); 123 | } 124 | 125 | public static String randomString() { 126 | return "" + randomLong(); 127 | } 128 | 129 | 130 | } 131 | 132 | /* 133 | * $Log: Rand.java,v $ 134 | * Revision 1.4 2007/12/07 12:57:40 justb 135 | * added log4j and make it the default logging method 136 | * 137 | * Revision 1.3 2007/11/23 21:10:17 justb 138 | * add hooks for custom logging (you can override DefaultLogger in pushlet.properties) 139 | * 140 | * Revision 1.2 2004/09/03 22:35:38 justb 141 | * Almost complete rewrite, just checking in now 142 | * 143 | * Revision 1.1 2004/03/10 12:21:27 justb 144 | * *** empty log message *** 145 | * 146 | * 147 | */ -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/util/Servlets.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2000 Just Objects B.V. 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | 4 | package nl.justobjects.pushlet.util; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | 9 | 10 | /** 11 | * Servlet utilities. 12 | * 13 | * @author Just van den Broecke - Just Objects © 14 | * @version $Id: Servlets.java,v 1.2 2007/11/23 21:10:17 justb Exp $ 15 | */ 16 | public class Servlets { 17 | 18 | /** 19 | * Get parameter; if not set or empty return null. 20 | */ 21 | public static String getParameter(HttpServletRequest aRequest, String aName) { 22 | return getParameter(aRequest, aName, null); 23 | } 24 | 25 | /** 26 | * Get parameter; if not set or empty return specified default value. 27 | */ 28 | public static String getParameter(HttpServletRequest aRequest, String aName, String aDefault) { 29 | String value = aRequest.getParameter(aName); 30 | if (value == null || value.length() == 0) { 31 | value = aDefault; 32 | } 33 | return value; 34 | } 35 | 36 | /** 37 | * Set HTTP headers to prevent caching. 38 | */ 39 | public static void setNoCacheHeaders(HttpServletResponse aResponse) { 40 | // Set to expire far in the past. 41 | aResponse.setHeader("Expires", "Sat, 6 May 1995 12:00:00 GMT"); 42 | 43 | // Set standard HTTP/1.1 no-cache headers. 44 | aResponse.setHeader("Cache-Control", "no-store, no-cache, must-revalidate"); 45 | 46 | // Set IE extended HTTP/1.1 no-cache headers (use addHeader). 47 | aResponse.addHeader("Cache-Control", "post-check=0, pre-check=0"); 48 | 49 | // Set standard HTTP/1.0 no-cache header. 50 | aResponse.setHeader("Pragma", "no-cache"); 51 | 52 | } 53 | 54 | } 55 | 56 | /* 57 | * $Log: Servlets.java,v $ 58 | * Revision 1.2 2007/11/23 21:10:17 justb 59 | * add hooks for custom logging (you can override DefaultLogger in pushlet.properties) 60 | * 61 | * Revision 1.1 2004/09/20 22:01:40 justb 62 | * more changes for new protocol 63 | * 64 | * 65 | */ 66 | 67 | 68 | -------------------------------------------------------------------------------- /src/nl/justobjects/pushlet/util/Sys.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2000 Just Objects B.V. 2 | // Distributable under LGPL license. See terms of license at gnu.org. 3 | 4 | package nl.justobjects.pushlet.util; 5 | 6 | 7 | import java.io.FileInputStream; 8 | import java.io.IOException; 9 | import java.text.CharacterIterator; 10 | import java.text.StringCharacterIterator; 11 | import java.util.Properties; 12 | 13 | /** 14 | * Utilities that interact with the underlying OS/JVM. 15 | * 16 | * @author Just van den Broecke 17 | * @version $Id: Sys.java,v 1.4 2007/11/10 14:17:18 justb Exp $ 18 | */ 19 | public class Sys { 20 | 21 | public static final String USERUNICAST_ALL="all"; 22 | public static final String USERUNICAST_FIRST="all"; 23 | public static final String USERUNICAST_LAST="all"; 24 | 25 | /** 26 | * Replace characters having special meaning inside HTML tags 27 | * with their escaped equivalents, using character entities such as '&'. 28 | *

29 | *

The escaped characters are : 30 | *

    31 | *
  • < 32 | *
  • > 33 | *
  • " 34 | *
  • ' 35 | *
  • \ 36 | *
  • & 37 | *
38 | *

39 | *

This method ensures that arbitrary text appearing inside a tag does not "confuse" 40 | * the tag. For example, HREF='Blah.do?Page=1&Sort=ASC' 41 | * does not comply with strict HTML because of the ampersand, and should be changed to 42 | * HREF='Blah.do?Page=1&Sort=ASC'. This is commonly seen in building 43 | * query strings. (In JSTL, the c:url tag performs this task automatically.) 44 | */ 45 | static public String forHTMLTag(String aTagFragment) { 46 | final StringBuffer result = new StringBuffer(); 47 | 48 | final StringCharacterIterator iterator = new StringCharacterIterator(aTagFragment); 49 | char character = iterator.current(); 50 | while (character != CharacterIterator.DONE) { 51 | if (character == '<') { 52 | result.append("<"); 53 | } else if (character == '>') { 54 | result.append(">"); 55 | } else if (character == '\"') { 56 | result.append("""); 57 | } else if (character == '\'') { 58 | result.append("'"); 59 | } else if (character == '\\') { 60 | result.append("\"); 61 | } else if (character == '&') { 62 | result.append("&"); 63 | } else { 64 | //the char is not a special one 65 | //add it to the result as is 66 | result.append(character); 67 | } 68 | character = iterator.next(); 69 | } 70 | return result.toString(); 71 | } 72 | 73 | /** 74 | * Load properties file from classpath. 75 | */ 76 | static public Properties loadPropertiesResource(String aResourcePath) throws IOException { 77 | try { 78 | // Use the class loader that loaded our class. 79 | // This is required where for reasons like security 80 | // multiple class loaders exist, e.g. BEA WebLogic. 81 | // Thanks to Lutz Lennemann 29-aug-2000. 82 | ClassLoader classLoader = Sys.class.getClassLoader(); 83 | 84 | Properties properties = new Properties(); 85 | 86 | // Try loading it. 87 | properties.load(classLoader.getResourceAsStream(aResourcePath)); 88 | return properties; 89 | } catch (Throwable t) { 90 | throw new IOException("failed loading Properties resource from " + aResourcePath); 91 | } 92 | } 93 | 94 | /** 95 | * Load properties file from file path. 96 | */ 97 | static public Properties loadPropertiesFile(String aFilePath) throws IOException { 98 | try { 99 | 100 | Properties properties = new Properties(); 101 | 102 | // Try loading it. 103 | properties.load(new FileInputStream(aFilePath)); 104 | return properties; 105 | } catch (Throwable t) { 106 | throw new IOException("failed loading Properties file from " + aFilePath); 107 | } 108 | } 109 | 110 | /** 111 | * Shorthand for current time. 112 | */ 113 | static public long now() { 114 | return System.currentTimeMillis(); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /src/修改说明.txt: -------------------------------------------------------------------------------- 1 | 2 | # JAVA 修改 3 | 4 | ### 1、中文无法传输问题修改 5 | 6 | 修改:`nl.justobjects.pushlet.core.XMLAdapter.java` 7 | 8 | 将 9 | ```JAVA 10 | private ServletOutputStream out = null; 11 | ``` 12 | 替换为 13 | ```JAVA 14 | private PrintWriter pw = null; 15 | ``` 16 | 并修改代码使用pw完成相应输出。 17 | 18 | 19 | 2、修改`nl.justobjects.pushlet.servlet.Pushlet.java` 20 | 21 | ```JAVA 22 | //添加如下代码 23 | // 点播推送用户和连接信息(用户名,所有连接集合_Set防止重复) 24 | public static final Map> UNICAST_USERS = new HashMap>(); 25 | 26 | /** 27 | * Generic request handler (GET+POST). 28 | */ 29 | protected void doRequest(Event anEvent, HttpServletRequest request, 30 | HttpServletResponse response) { 31 | 32 | //修改doRequest,添加如下代码 33 | String unicastType = Config.getProperty("unicast.type"); 34 | 35 | // 获得pushlet的SessionID 36 | String sessionId = anEvent.getField(P_ID); 37 | if (sessionId != null) { 38 | // 获得当前的用户名(提前存入了Session) 39 | String userId = anEvent.getField("userId"); 40 | if (userId != null) { 41 | // 用户多会话点播 42 | // 为用户会话初始化会话集合列表 43 | if (UNICAST_USERS.get(userId) == null) { 44 | UNICAST_USERS.put(userId, new ArrayList()); 45 | } 46 | //登记会话,不存在会话,则添加 47 | if(!UNICAST_USERS.get(userId).contains(sessionId)){ 48 | UNICAST_USERS.get(userId).add(sessionId); 49 | } 50 | } 51 | } 52 | 53 | 54 | //其他代码 55 | } 56 | ``` 57 | 58 | 3、修改`nl.justobjects.pushlet.core.Dispatcher.java` 59 | 60 | 添加新方法: 61 | ```JAVA 62 | /** 63 | * 为指定用户点播 64 | * 65 | * @param event 66 | * @param userId 67 | */ 68 | public synchronized void unicastUserId(Event event, String userId) { 69 | // 用户已连接 70 | if (Pushlet.UNICAST_USERS.get(userId) != null) { 71 | String unicastType = Config.getProperty("unicast.type"); 72 | //点播类型默认为all,所有连接 73 | if(unicastType==null){ 74 | unicastType="all"; 75 | } 76 | unicastUserId(event,userId,unicastType); 77 | } 78 | } 79 | 80 | /** 81 | * 为指定用户点播 82 | * 83 | * @param event 84 | * @param userId 85 | * @param unicastType 86 | */ 87 | public synchronized void unicastUserId(Event event, String userId, 88 | String unicastType) { 89 | // 用户已连接 90 | if (Pushlet.UNICAST_USERS.get(userId) != null) { 91 | 92 | List userSessions = Pushlet.UNICAST_USERS.get(userId); 93 | // 已退出的用户 94 | List removeSessions = new ArrayList(); 95 | 96 | if (unicastType.equalsIgnoreCase("first") 97 | || unicastType.equalsIgnoreCase("last")) { 98 | if (userSessions.size() > 0) { 99 | String sessionId = userSessions.get(0); // first 100 | if (unicastType.equalsIgnoreCase("last")) { 101 | sessionId = userSessions.get(userSessions.size() - 1); // last 102 | } 103 | if (SessionManager.getInstance().getSession(sessionId) != null) { 104 | // 点播信息给用户的会话 105 | unicast(event, sessionId); 106 | } else { 107 | // 记录断开连接的用户 108 | removeSessions.add(sessionId); 109 | } 110 | } 111 | 112 | } else { 113 | // all 114 | for (int i = 0; i < userSessions.size(); i++) { 115 | String sessionId = userSessions.get(i); 116 | // 用户会话有效(sessionID存在) 117 | if (SessionManager.getInstance().getSession(sessionId) != null) { 118 | // 点播信息给用户的会话 119 | unicast(event, sessionId); 120 | } else { 121 | // 记录断开连接的用户 122 | removeSessions.add(sessionId); 123 | } 124 | } 125 | } 126 | // 用户离线,移除绑定的用户会话记录 127 | userSessions.removeAll(removeSessions); 128 | // 如果用户都已离开,则移除 129 | if (userSessions.size() == 0) { 130 | Pushlet.UNICAST_USERS.remove(userId); 131 | } 132 | } 133 | } 134 | ``` 135 | 136 | 4、修改`nl.justobjects.pushlet.core.SessionManager.java` 137 | 138 | ```JAVA 139 | /** 140 | * Register session for removal. 141 | */ 142 | public Session removeSession(Session aSession) { 143 | synchronized (mutex) { 144 | Session session = (Session) sessions.remove(aSession.getId()); 145 | if (session != null) { 146 | info(session.getId() + " at " + session.getAddress() + " removed "); 147 | } 148 | //添加代码 149 | //已退出的用户 150 | List removeSessions = new ArrayList(); 151 | for(Entry> e:Pushlet.UNICAST_USERS.entrySet()){ 152 | if(e.getValue().contains(session.getId())){ 153 | e.getValue().remove(session.getId()); //从用户广播注册列表移除当前会话 154 | } 155 | if(e.getValue().size()==0){ 156 | removeSessions.add(e.getKey()); 157 | } 158 | } 159 | //清除无效userId(已无会话) 160 | for(String userId:removeSessions){ 161 | Pushlet.UNICAST_USERS.remove(userId); 162 | } 163 | sessionCacheDirty = true; 164 | return session; 165 | } 166 | } 167 | ``` 168 | 169 | 4、修改`nl.justobjects.pushlet.util.Sys.java` 170 | 添加如下代码: 171 | ```JAV 172 | public static final String USERUNICAST_ALL="all"; 173 | public static final String USERUNICAST_FIRST="all"; 174 | public static final String USERUNICAST_LAST="all"; 175 | ``` 176 | 177 | # JAVASCRIPT 修改: 178 | 179 | 添加`userID`属性,在`_onEvent`函数的`refres`h判断中,注册服务器连接成功事件 `PL._doCallback(event, window.onConnected); ` 180 | 181 | ```JS 182 | var PL = { 183 | //添加userID标识 184 | userId:'', 185 | //.... 186 | 187 | _onEvent: function (event) { 188 | //... 189 | if (eventType == 'data') { 190 | //... 191 | } else if (eventType == 'refresh') { 192 | if (PL.state < PL.STATE_LISTENING) { 193 | PL._setStatus('not refreshing state=' + PL.STATE_LISTENING); 194 | } 195 | var timeout = event.get('p_wait'); 196 | setTimeout(function () { 197 | PL._doRequest('refresh'); 198 | //添加服务器连接成功事件 199 | PL._doCallback(event, window.onConnected); 200 | }, timeout); 201 | return; 202 | } else if (eventType == 'error') { 203 | //... 204 | } 205 | //... 206 | }, 207 | //... 208 | } 209 | ``` --------------------------------------------------------------------------------