summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--WebContent/VAADIN/atmosphere.min.js12
-rw-r--r--WebContent/VAADIN/portal.min.js9
-rw-r--r--client/src/com/vaadin/client/ApplicationConfiguration.java15
-rw-r--r--client/src/com/vaadin/client/ApplicationConnection.java56
-rw-r--r--client/src/com/vaadin/client/communication/PushConnection.java135
-rw-r--r--server/ivy.xml4
-rw-r--r--server/src/com/vaadin/server/BootstrapHandler.java20
-rw-r--r--server/src/com/vaadin/server/Constants.java8
-rw-r--r--server/src/com/vaadin/server/DefaultDeploymentConfiguration.java42
-rw-r--r--server/src/com/vaadin/server/DeploymentConfiguration.java10
-rw-r--r--server/src/com/vaadin/server/ServletPortletHelper.java7
-rw-r--r--server/src/com/vaadin/server/VaadinService.java1
-rw-r--r--server/src/com/vaadin/server/VaadinServletService.java5
-rw-r--r--server/src/com/vaadin/server/VaadinSession.java57
-rw-r--r--server/src/com/vaadin/server/communication/MetadataWriter.java15
-rw-r--r--server/src/com/vaadin/server/communication/PushConnection.java140
-rw-r--r--server/src/com/vaadin/server/communication/PushHandler.java184
-rw-r--r--server/src/com/vaadin/server/communication/PushRequestHandler.java95
-rw-r--r--server/src/com/vaadin/server/communication/UIInitHandler.java7
-rw-r--r--server/src/com/vaadin/server/communication/UidlRequestHandler.java2
-rw-r--r--server/src/com/vaadin/server/communication/UidlWriter.java12
-rw-r--r--server/src/com/vaadin/ui/UI.java48
-rw-r--r--shared/src/com/vaadin/shared/ApplicationConstants.java2
-rw-r--r--shared/src/com/vaadin/shared/communication/PushMode.java55
-rw-r--r--uitest/ivy.xml10
25 files changed, 913 insertions, 38 deletions
diff --git a/WebContent/VAADIN/atmosphere.min.js b/WebContent/VAADIN/atmosphere.min.js
new file mode 100644
index 0000000000..64f6604951
--- /dev/null
+++ b/WebContent/VAADIN/atmosphere.min.js
@@ -0,0 +1,12 @@
+/*
+ * Atmosphere.js
+ * https://github.com/Atmosphere/atmosphere
+ *
+ * Requires Portal 1.0
+ * https://github.com/flowersinthesand/portal
+ *
+ * Copyright 2012-2013, Donghwan Kim
+ * Licensed under the Apache License, Version 2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ */
+(function(){function l(a,b){var c,f,g=b.headers||{};if(b.readResponsesHeaders)for(c in b.lastTimestamp=(a.getResponseHeader("X-Cache-Date")||"").split(" ").pop(),b.uuid=(a.getResponseHeader("X-Atmosphere-tracking-id")||"").split(" ").pop(),g)(f=a.getResponseHeader(c))&&(g[c]=f)}function m(a,b){var c,f,g=b.headers||{};b.dropAtmosphereHeaders||(a.setRequestHeader("X-Atmosphere-Framework",n),a.setRequestHeader("X-Atmosphere-Transport",b.transport),a.setRequestHeader("X-Cache-Date",b.lastTimestamp||0), b.trackMessageLength&&a.setRequestHeader("X-Atmosphere-TrackMessageSize","true"),b.contentType&&a.setRequestHeader("Content-Type",b.contentType),a.setRequestHeader("X-Atmosphere-tracking-id",b.uuid));for(c in g)f=g[c],(f=portal.support.isFunction(f)?f.call(null,a,b):f)&&a.setRequestHeader(c,f)}var n="1.1",k={},p=portal.support.now();k.subscribe=function(a){a=new k.AtmosphereRequest(a);a.open();return a};k.unsubscribe=portal.finalize;k.AtmosphereRequest=function(a){var b,c;a=portal.support.extend({url:"", connectTimeout:-1,reconnectInterval:0,timeout:3E5,method:"GET",fallbackMethod:"GET",headers:{},maxRequest:-1,transport:"long-polling",fallbackTransport:"streaming",dispatchUrl:null,webSocketPathDelimiter:"@@",webSocketBinaryType:null,enableXDR:!1,rewriteURL:!1,attachHeadersAsQueryString:!0,withCredentials:!1,trackMessageLength:!1,messageDelimiter:"|",shared:!1,lastTimestamp:0,readResponsesHeaders:!0,dropAtmosphereHeaders:!0,contentType:"",uuid:0,executeCallbackBeforeReconnect:!1},a);this.open=function(){function f(){g(); j=setTimeout(function(){b.fire("close","idletimeout")},a.timeout)}function g(){clearTimeout(j)}function h(a){return{"long-polling":"longpoll",streaming:"stream",jsonp:"longpolljsonp",sse:"sse",websocket:"ws",session:"session",test:"test"}[a]}var j;b=portal.open(a.url,{atrequest:a,method:a.method,transports:[h(a.transport)],timeout:a.connectTimeout,credentials:a.withCredentials,sharing:a.shared,params:a.headers,longpollTest:!1,urlBuilder:function(b,d){if(!a.attachHeadersAsQueryString)return b;delete d.id; delete d.transport;delete d.heartbeat;delete d.lastEventId;portal.support.extend(d,{"X-Atmosphere-tracking-id":a.uuid,"X-Atmosphere-Framework":n,"X-Atmosphere-Transport":a.transport,"X-Cache-Date":a.lastTimestamp||0});a.trackMessageLength&&(d["X-Atmosphere-TrackMessageSize"]=!0);a.contentType&&(d["Content-Type"]=a.contentType);return b+(/\?/.test(b)?"&":"?")+portal.support.param(d)},reconnect:function(b,d){return-1===a.maxRequest||d<a.maxRequest?a.reconnectInterval:!1},xdrURL:a.enableXDR&&function(e){return(a.rewriteURL|| portal.defaults.xdrURL||function(){}).call(a.rewriteURL?window:b,e)||e},inbound:function(e){var d,c,j=[];0<a.timeout&&f();if(a.trackMessageLength){b.data("data")&&(e=b.data("data")+e);d=0;for(c=e.indexOf(a.messageDelimiter);-1!==c;){d=e.substring(d,c);e=e.substring(c+a.messageDelimiter.length,e.length);if(!e||e.length<d)break;c=e.indexOf(a.messageDelimiter);j.push(e.substring(0,d))}b.data("data",!j.length||-1!==c&&e&&d!==e.length?d+a.messageDelimiter+e:"")}else j.push(e);for(e=0;e<j.length;e++)j[e]= {type:"message",data:j[e]};return j},outbound:function(b){0<a.timeout&&f();return b.data},streamParser:function(a){return[a.replace(/^\s+/g,"")]},initIframe:function(a){var b;b=a.contentDocument||a.contentWindow.document;if(!b.body||!b.body.firstChild||"pre"!==b.body.firstChild.nodeName.toLowerCase())a=b.head||b.getElementsByTagName("head")[0]||b.documentElement||b,b=b.createElement("script"),b.text="document.write('<plaintext>')",a.insertBefore(b,a.firstChild),a.removeChild(b)}});b.on({connecting:function(){b.data("t1", {ws:"websocket",sse:"sse",streamxhr:"streaming",streamxdr:"streaming",streamiframe:"streaming",longpollajax:"long-polling",longpollxdr:"long-polling",longpolljsonp:"jsonp",session:"session",test:"test"}[b.data("transport")])},open:function(){var e={status:200,responseBody:"",headers:[],state:"messageReceived",transport:b.data("t1"),error:null,request:a};0<a.timeout&&(f(),b.one("close",g));if(c){if(e.state="re-opening",a.onReconnect)a.onReconnect(a,e)}else{e.state="opening";if(a.onOpen)a.onOpen(e); c=!0}a.callback&&a.callback(e)},message:function(c){var d={status:200,responseBody:c,headers:[],state:"messageReceived",transport:b.data("t1"),error:null,request:a};b.data("lastData",c);if(a.onMessage)a.onMessage(d);a.callback&&a.callback(d)},close:function(e){var d={status:200,responseBody:"",headers:[],state:"messageReceived",transport:b.data("t1"),error:null,request:a};switch(e){case "aborted":d.status=408;d.state="unsubscribe";break;case "done":case "timeout":d.status=!c?501:200;d.state="closed"; break;case "error":d.status=500;d.state="error";break;case "notransport":if(a.onTransportFailure)a.onTransportFailure(e,a);a.method=a.fallbackMethod;a.transport=a.fallbackTransport;b.option("method",a.method);b.option("transports",[h(a.transport)])}if("error"===e){if(a.onError)a.onError(d)}else if(a.onClose)a.onClose(d);a.callback&&a.callback(d);a.executeCallbackBeforeReconnect&&(b.fire("message",b.data("lastData")),b.option("method",a.method),b.option("transports",[h(a.transport)]))},waiting:function(){a.executeCallbackBeforeReconnect|| b.fire("message",b.data("lastData"))},session:function(c){if(c.from!==b.option("id")&&a.onLocalMessage)a.onLocalMessage(c.message)}})};this.push=function(a,c){var h=b.option("dispatchUrl");b.option("dispatchUrl",c);b.send("message",a);b.option("dispatchUrl",h)};this.pushLocal=function(a){b.broadcast("session",{from:b.option("id"),data:a})}};portal.support.extend(portal.transports,{ws:function(a,b){var c,f,g=window.WebSocket||window.MozWebSocket;if(g)return{feedback:!0,open:function(){var h=portal.support.getAbsoluteURL(a.data("url")).replace(/^http/, "ws");a.data("url",h);c=new g(h);b.atrequest.webSocketBinaryType&&(c.binaryType=b.atrequest.webSocketBinaryType);c.onopen=function(b){a.data("event",b).fire("open")};c.onmessage=function(b){a.data("event",b)._fire(b.data)};c.onerror=function(b){a.data("event",b).fire("close",f?"aborted":"error")};c.onclose=function(b){a.data("event",b).fire("close",f?"aborted":b.wasClean?"done":"error")}},send:function(a){var f=b.atrequest.dispatchUrl,e=b.atrequest.webSocketPathDelimiter;console.log(b.atrequest); c.send((f?e+f+e:"")+a)},close:function(){f=!0;c.close()}}},httpbase:function(a,b){function c(){h.length?f(b.url+(b.dispatchUrl||""),h.shift()):g=!1}var f,g,h=[];f=!b.crossDomain||portal.support.corsable?function(a,e){var d=portal.support.xhr();d.onreadystatechange=function(){4===d.readyState&&(l(d,b.atrequest),c())};d.open("POST",a);m(d,b.atrequest);portal.support.corsable&&(d.withCredentials=b.credentials);d.send(e)}:window.XDomainRequest&&b.xdrURL&&b.xdrURL.call(a,"t")?function(f,e){var d=new window.XDomainRequest; d.onload=d.onerror=c;d.open("POST",b.xdrURL.call(a,f));d.send(e)}:function(a,b){var d=document.createElement("form");d.action=a;d.target="socket-"+ ++p;d.method="POST";d.enctype=d.encoding="text/plain";d.acceptCharset="UTF-8";d.style.display="none";d.innerHTML='<textarea name="data"></textarea><iframe name="'+d.target+'"></iframe>';d.firstChild.value=b;portal.support.on(d.lastChild,"load",function(){document.body.removeChild(d);c()});document.body.appendChild(d);d.submit()};return{send:function(a){h.push(a); g||(g=!0,c())}}},streamxhr:function(a,b){var c;if(!(portal.support.browser.msie&&10>+portal.support.browser.version||b.crossDomain&&!portal.support.corsable))return portal.support.extend(portal.transports.httpbase(a,b),{open:function(){var f;c=portal.support.xhr();c.onreadystatechange=function(){function g(){var b=a.data("index"),f=c.responseText.length;b?f>b&&a._fire(c.responseText.substring(b,f),!0):a.fire("open")._fire(c.responseText,!0);a.data("index",f)}2===c.readyState?l(c,b.atrequest):3=== c.readyState&&200===c.status?portal.support.browser.opera&&!f?f=portal.support.iterate(g):g():4===c.readyState&&(f&&f(),a.fire("close",200===c.status?"done":"error"))};c.open("GET",a.data("url"));portal.support.corsable&&(c.withCredentials=b.credentials);m(c,b.atrequest);c.send(null)},close:function(){c.abort()}})},longpollajax:function(a,b){var c,f,g=0;if(!b.crossDomain||portal.support.corsable)return portal.support.extend(portal.transports.httpbase(a,b),{open:function(){function h(){var j=a.buildURL(!g? "open":"poll",{count:++g});a.data("url",j);c=portal.support.xhr();c.onreadystatechange=function(){var e;!f&&4===c.readyState&&(200===c.status?(l(c,b.atrequest),(e=c.responseText)||1===g?(1===g&&a.fire("open"),e&&a._fire(e),h()):a.fire("close","done")):a.fire("close","error"))};c.open("GET",j);m(c,b.atrequest);portal.support.corsable&&(c.withCredentials=b.credentials);c.send(null)}b.longpollTest?h():setTimeout(function(){a.fire("open");h()},50)},close:function(){f=!0;c.abort()}})}});portal.support.on(window, "keypress",function(a){27===a.which&&a.preventDefault()});window.atmosphere=k})(); \ No newline at end of file
diff --git a/WebContent/VAADIN/portal.min.js b/WebContent/VAADIN/portal.min.js
new file mode 100644
index 0000000000..83228d205c
--- /dev/null
+++ b/WebContent/VAADIN/portal.min.js
@@ -0,0 +1,9 @@
+/*
+ * Portal v1.0
+ * http://github.com/flowersinthesand/portal
+ *
+ * Copyright 2011-2013, Donghwan Kim
+ * Licensed under the Apache License, Version 2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ */
+(function(){function z(a){var c=[],b,e,g,d,h,m,l=function(b,n){n=n||[];e=!a||[b,n];g=!0;m=d||0;d=0;for(h=c.length;m<h;m++)c[m].apply(b,n);g=!1};return{add:function(a){var n=c.length;c.push(a);g?h=c.length:!b&&(e&&!0!==e)&&(d=n,l(e[0],e[1]))},remove:function(a){var b;for(b=0;b<c.length;b++)if(a===c[b]||a.guid&&a.guid===c[b].guid)g&&b<=h&&(h--,b<=m&&m--),c.splice(b--,1)},fire:function(c,d){!b&&(!g&&(!a||!e))&&l(c,d)},lock:function(){b=!0},locked:function(){return!!b},unlock:function(){b=e=g=d=h=m=void 0}}} var v,w,e={},q={},B=[],x=Object.prototype.toString,C=Object.prototype.hasOwnProperty,y=Array.prototype.slice;e.support={now:function(){return(new Date).getTime()},isArray:function(a){return"[object Array]"===x.call(a)},isBinary:function(a){a=x.call(a);return"[object Blob]"===a||"[object ArrayBuffer]"===a},isFunction:function(a){return"[object Function]"===x.call(a)},getAbsoluteURL:function(a){var c=document.createElement("div");c.innerHTML='<a href="'+a+'"/>';return encodeURI(decodeURI(c.firstChild.href))}, iterate:function(a){var c;(function f(){c=setTimeout(function(){!1!==a()&&f()},1)})();return function(){clearTimeout(c)}},each:function(a,c){var b;for(b=0;b<a.length;b++)c(b,a[b])},extend:function(a){var c,b,e;for(c=1;c<arguments.length;c++)if(null!=(b=arguments[c]))for(e in b)a[e]=b[e];return a},on:function(a,c,b){a.addEventListener?a.addEventListener(c,b,!1):a.attachEvent&&a.attachEvent("on"+c,b)},off:function(a,c,b){a.removeEventListener?a.removeEventListener(c,b,!1):a.detachEvent&&a.detachEvent("on"+ c,b)},param:function(a){function c(a,b){b=e.support.isFunction(b)?b():null==b?"":b;g.push(encodeURIComponent(a)+"="+encodeURIComponent(b))}function b(a,h){var f;if(e.support.isArray(h))e.support.each(h,function(e,h){/\[\]$/.test(a)?c(a,h):b(a+"["+("object"===typeof h?e:"")+"]",h)});else if("[object Object]"===x.call(h))for(f in h)b(a+"["+f+"]",h[f]);else c(a,h)}var f,g=[];for(f in a)b(f,a[f]);return g.join("&").replace(/%20/g,"+")},xhr:function(){try{return new window.XMLHttpRequest}catch(a){try{return new window.ActiveXObject("Microsoft.XMLHTTP")}catch(c){}}}, parseJSON:function(a){return!a?null:window.JSON&&window.JSON.parse?window.JSON.parse(a):(new Function("return "+a))()},stringifyJSON:function(a){function c(a){return'"'+a.replace(e,function(a){var b=g[a];return"string"===typeof b?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"'}function b(a){return 10>a?"0"+a:a}var e=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,g={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f", "\r":"\\r",'"':'\\"',"\\":"\\\\"};return window.JSON&&window.JSON.stringify?window.JSON.stringify(a):function h(a,e){var f,n,g,k=e[a];g=typeof k;k&&("object"===typeof k&&"function"===typeof k.toJSON)&&(k=k.toJSON(a),g=typeof k);switch(g){case "string":return c(k);case "number":return isFinite(k)?String(k):"null";case "boolean":return String(k);case "object":if(!k)return"null";switch(x.call(k)){case "[object Date]":return isFinite(k.valueOf())?'"'+k.getUTCFullYear()+"-"+b(k.getUTCMonth()+1)+"-"+b(k.getUTCDate())+ "T"+b(k.getUTCHours())+":"+b(k.getUTCMinutes())+":"+b(k.getUTCSeconds())+'Z"':"null";case "[object Array]":n=k.length;g=[];for(f=0;f<n;f++)g.push(h(f,k)||"null");return"["+g.join(",")+"]";default:g=[];for(f in k)C.call(k,f)&&(n=h(f,k))&&g.push(c(f)+":"+n);return"{"+g.join(",")+"}"}}}("",{"":a})},browser:{},storage:!(!window.localStorage||!window.StorageEvent)};e.support.corsable="withCredentials"in e.support.xhr();v=e.support.now();var t=navigator.userAgent.toLowerCase(),t=/(chrome)[ \/]([\w.]+)/.exec(t)|| /(webkit)[ \/]([\w.]+)/.exec(t)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(t)||/(msie) ([\w.]+)/.exec(t)||0>t.indexOf("compatible")&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(t)||[];e.support.browser[t[1]||""]=!0;e.support.browser.version=t[2]||"0";if(e.support.browser.msie||e.support.browser.mozilla&&"1"===e.support.browser.version.split(".")[0])e.support.storage=!1;e.find=function(a){var c;if(!arguments.length){for(c in q)if(q[c])return q[c];return null}return q[e.support.getAbsoluteURL(a)]||null};e.open= function(a,c){var b=a=e.support.getAbsoluteURL(a),f,g=a,d,h,m,l={},A=0,n={},r=[],k,u,s,p={};f=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/.exec(g.toLowerCase());var j={option:function(a,b){if(void 0===b)return d[a];d[a]=b;return this},data:function(a,b){if(void 0===b)return p[a];p[a]=b;return this},state:function(){return m},on:function(a,b){var c;if("object"===typeof a){for(c in a)j.on(c,a[c]);return this}c=l[a];if(!c){if(l.message.locked())return this;c=l[a]=z();c.order=l.message.order}c.add(b); return this},off:function(a,b){var c=l[a];c&&c.remove(b);return this},one:function(a,b){function c(){j.off(a,c);b.apply(j,arguments)}b.guid=b.guid||v++;c.guid=b.guid;return j.on(a,c)},fire:function(a){var b=l[a];b&&b.fire(j,y.call(arguments,1));return this},open:function(){var a,b,c=function(){var a,c;if(!b){b=!0;for(a=p.candidates=y.call(d.transports);!h&&a.length;)c=a.shift(),p.transport=c,p.url=j.buildURL("open"),h=e.transports[c](j,d);s&&s++;h?(j.fire("connecting"),h.open()):j.fire("close","notransport")}}, f=function(){b||(b=!0,j.fire("close","canceled"))};k&&clearTimeout(k);p={};for(a in l)l[a].unlock();h=void 0;m="preparing";d.sharing&&(p.transport="session",h=e.transports.session(j,d));h?c():d.prepare.call(j,c,f,d);return this},send:function(a,b,c,f){var g;if("opened"!==m)return r.push(arguments),this;g={id:++A,socket:d.id,type:a,data:b,reply:!(!c&&!f)};g.reply&&("session"===p.transport?(g.doneCallback=c,g.failCallback=f):n[A]={done:c,fail:f});h.send(e.support.isBinary(b)?b:d.outbound.call(j,g)); return this},close:function(){var a,b;d.reconnect=!1;k&&clearTimeout(k);if(w||!h||!h.feedback)j.fire("close",w?"error":"aborted"),d.notifyAbort&&"session"!==p.transport&&(b=document.head||document.getElementsByTagName("head")[0]||document.documentElement,a=document.createElement("script"),a.async=!1,a.src=j.buildURL("abort"),a.onload=a.onreadystatechange=function(){if(!a.readyState||/loaded|complete/.test(a.readyState))a.onload=a.onreadystatechange=null,a.parentNode&&a.parentNode.removeChild(a)}, b.insertBefore(a,b.firstChild));h&&h.close();return this},broadcast:function(a,b){var c=p.broadcastable;c&&c.broadcast({type:"fire",data:{type:a,data:b}});return this},_fire:function(a,b){var c;if(b){for(a=d.streamParser.call(j,a);a.length;)j._fire(a.shift());return this}e.support.isBinary(a)?c=[{type:"message",data:a}]:(c=d.inbound.call(j,a),c=null==c?[]:!e.support.isArray(c)?[c]:c);p.lastEventIds=[];e.support.each(c,function(a,b){var c,e=[b.type,b.data];d.lastEventId=b.id;p.lastEventIds.push(b.id); b.reply&&e.push(function(a){c||(c=!0,j.send("reply",{id:b.id,data:a}))});j.fire.apply(j,e).fire("_message",e)});return this},buildURL:function(a,b){var c="open"===a?{transport:p.transport,heartbeat:d.heartbeat,lastEventId:d.lastEventId}:"poll"===a?{transport:p.transport,lastEventIds:p.lastEventIds&&p.lastEventIds.join(","),lastEventId:d.lastEventId}:{};e.support.extend(c,{id:d.id,_:v++},d.params&&d.params[a],b);return d.urlBuilder.call(j,g,c,a)}};d=e.support.extend({},e.defaults,c);c&&c.transports&& (d.transports=y.call(c.transports));d.url=g;d.id=d.idGenerator.call(j);d.crossDomain=!(!f||!(f[1]!=location.protocol||f[2]!=location.hostname||(f[3]||("http:"===f[1]?80:443))!=(location.port||("http:"===location.protocol?80:443))));e.support.each(["connecting","open","message","close","waiting"],function(a,b){l[b]=z("message"!==b);l[b].order=a;var c=j[b],d=function(a){return j.on(b,a)};j[b]=!c?d:function(a){return(e.support.isFunction(a)?d:c).apply(this,arguments)}});j.on({connecting:function(){function a(){clearTimeout(b)} m="connecting";var b;0<d.timeout&&(b=setTimeout(function(){h.close();j.fire("close","timeout")},d.timeout),j.one("open",a).one("close",a));if(d.sharing&&"session"!==p.transport){var c=function(a){a=e.support.parseJSON(a);var b=a.data;if(a.target){if("p"===a.target)switch(a.type){case "send":j.send(b.type,b.data,b.doneCallback,b.failCallback);break;case "close":j.close()}}else"fire"===a.type&&j.fire(b.type,b.data)},f=function(a){k.broadcast({target:"c",type:"message",data:a})},n=function(){document.cookie= encodeURIComponent(r)+"="+encodeURIComponent(e.support.stringifyJSON({ts:e.support.now()+1,heir:(k.get("children")||[])[0]}))},l,k,r="socket-"+g,s={storage:function(){if(e.support.storage){var a=window.localStorage;return{init:function(){function b(a){a.key===r&&a.newValue&&c(a.newValue)}e.support.on(window,"storage",b);j.one("close",function(){e.support.off(window,"storage",b);j.one("close",function(){a.removeItem(r);a.removeItem(r+"-opened");a.removeItem(r+"-children")})})},broadcast:function(b){var d= e.support.stringifyJSON(b);a.setItem(r,d);setTimeout(function(){c(d)},50)},get:function(b){return e.support.parseJSON(a.getItem(r+"-"+b))},set:function(b,c){a.setItem(r+"-"+b,e.support.stringifyJSON(c))}}}},windowref:function(){var a=r.replace(/\W/g,""),b=document.getElementById(a),d;b||(b=document.createElement("div"),b.id=a,b.style.display="none",b.innerHTML='<iframe name="'+a+'" />',document.body.appendChild(b));d=b.firstChild.contentWindow;return{init:function(){d.callbacks=[c];d.fire=function(a){var b; for(b=0;b<d.callbacks.length;b++)d.callbacks[b](a)}},broadcast:function(a){!d.closed&&d.fire&&d.fire(e.support.stringifyJSON(a))},get:function(a){return!d.closed?d[a]:null},set:function(a,b){d.closed||(d[a]=b)}}}};k=s.storage()||s.windowref();k.init();p.broadcastable=k;k.set("children",[]);k.set("opened",!1);n();l=setInterval(n,1E3);j.on("_message",f).one("open",function(){k.set("opened",!0);k.broadcast({target:"c",type:"open"})}).one("close",function(a){clearInterval(l);document.cookie=encodeURIComponent(r)+ "=; expires=Thu, 01 Jan 1970 00:00:00 GMT";k.broadcast({target:"c",type:"close",data:{reason:a,heir:!w?d.id:(k.get("children")||[])[0]}});j.off("_message",f)})}},open:function(){function a(){c=setTimeout(function(){j.send("heartbeat").one("heartbeat",function(){b();a()});c=setTimeout(function(){h.close();j.fire("close","error")},d._heartbeat)},d.heartbeat-d._heartbeat)}function b(){clearTimeout(c)}m="opened";var c;d.heartbeat>d._heartbeat&&(a(),j.one("close",b));l.connecting.lock();for(k=u=s=null;r.length;)j.send.apply(j, r.shift())},close:function(){m="closed";var a,b,c=l.close.order;for(a in l)b=l[a],b.order<c&&b.lock();if(d.reconnect)j.one("close",function(){s=s||1;u=d.reconnect.call(j,u,s);!1!==u&&(k=setTimeout(function(){j.open()},u),j.fire("waiting",u,s))})},waiting:function(){m="waiting"},reply:function(a){var b=a.id,c=a.data;a=a.exception;var d=n[b];if(d&&(a=a?d.fail:d.done))e.support.isFunction(a)?a.call(j,c):j.fire(a,c).fire("_message",[a,c]),delete n[b]}});f=j.open();q[b]=f;return e.find(a)};e.defaults= {transports:["ws","sse","stream","longpoll"],timeout:!1,heartbeat:!1,lastEventId:0,sharing:!1,prepare:function(a){a()},reconnect:function(a){return 2*(a||250)},idGenerator:function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(a){var c=16*Math.random()|0;return("x"===a?c:c&3|8).toString(16)})},urlBuilder:function(a,c,b){return a+(/\?/.test(a)?"&":"?")+"when="+b+"&"+e.support.param(c)},inbound:e.support.parseJSON,outbound:e.support.stringifyJSON,credentials:!1,notifyAbort:!1, xdrURL:function(a){var c=/(?:^|; )(JSESSIONID|PHPSESSID)=([^;]*)/.exec(document.cookie);switch(c&&c[1]){case "JSESSIONID":return a.replace(/;jsessionid=[^\?]*|(\?)|$/,";jsessionid="+c[2]+"$1");case "PHPSESSID":return a.replace(/\?PHPSESSID=[^&]*&?|\?|$/,"?PHPSESSID="+c[2]+"&").replace(/&$/,"");default:return!1}},streamParser:function(a){var c=/\r\n|[\r\n]/g,b=[],e=this.data("data"),g=[],d=0,h;for(a=a.replace(/^\s+/g,"");h=c.exec(a);)b.push(a.substring(d,h.index)),d=h.index+h[0].length;b.push(a.length=== d?"":a.substring(d));e||(e=[],this.data("data",e));for(d=0;d<b.length;d++)(a=b[d])?/^data:\s/.test(a)?e.push(a.substring(6)):e[e.length-1]+=a:(g.push(e.join("\n")),e=[],this.data("data",e));return g},_heartbeat:5E3,longpollTest:!0};e.transports={session:function(a,c){function b(a,b){var c,e=a.length;for(c=0;c<e;c++)a[c]===b&&a.splice(c,1);return e!==a.length}function f(b){b=e.support.parseJSON(b);var d=b.data;if(b.target){if("c"===b.target)switch(b.type){case "open":a.fire("open");break;case "close":h|| (h=!0,"aborted"===d.reason?a.close():d.heir===c.id?a.fire("close",d.reason):setTimeout(function(){a.fire("close",d.reason)},100));break;case "message":if("connecting"===a.state())a.one("open",function(){a.fire.apply(a,d)});else a.fire.apply(a,d)}}else"fire"===b.type&&a.fire(d.type,d.data)}function g(){var a=RegExp("(?:^|; )("+encodeURIComponent(l)+")=([^;]*)").exec(document.cookie);if(a)return e.support.parseJSON(decodeURIComponent(a[2]))}var d,h,m,l="socket-"+c.url,q={storage:function(){function d(a){return e.support.parseJSON(h.getItem(l+ "-"+a))}if(e.support.storage){var h=window.localStorage;return{init:function(){function g(a){a.key===l&&a.newValue&&f(a.newValue)}var m=d("children").concat([c.id]);h.setItem(l+"-children",e.support.stringifyJSON(m));e.support.on(window,"storage",g);a.one("close",function(){var a=d("children");e.support.off(window,"storage",g);a&&b(a,c.id)&&h.setItem(l+"-children",e.support.stringifyJSON(a))});return d("opened")},broadcast:function(a){var b=e.support.stringifyJSON(a);h.setItem(l,b);setTimeout(function(){f(b)}, 50)}}}},windowref:function(){var d=window.open("",l.replace(/\W/g,""));if(d&&!d.closed&&d.callbacks)return{init:function(){d.callbacks.push(f);d.children.push(c.id);a.one("close",function(){h||(b(d.callbacks,f),b(d.children,c.id))});return d.opened},broadcast:function(a){!d.closed&&d.fire&&d.fire(e.support.stringifyJSON(a))}}}};if((d=g())&&!(1E3<e.support.now()-d.ts))if(m=q.storage()||q.windowref())return a.data("broadcastable",m),{open:function(){var b,h=c.timeout,l=c.heartbeat,q=c.outbound;c.timeout= c.heartbeat=!1;c.outbound=function(a){return a};b=setInterval(function(){var a=d;d=g();(!d||a.ts===d.ts)&&f(e.support.stringifyJSON({target:"c",type:"close",data:{reason:"error",heir:a.heir}}))},1E3);a.one("close",function(){clearInterval(b);c.timeout=h;c.heartbeat=l;c.outbound=q});m.init()&&setTimeout(function(){a.fire("open")},50)},send:function(a){m.broadcast({target:"p",type:"send",data:a})},close:function(){w||m.broadcast({target:"p",type:"close"})}}},ws:function(a){var c,b,f=window.WebSocket|| window.MozWebSocket;if(f)return{feedback:!0,open:function(){var g=e.support.getAbsoluteURL(a.data("url")).replace(/^http/,"ws");a.data("url",g);c=new f(g);c.onopen=function(b){a.data("event",b).fire("open")};c.onmessage=function(b){a.data("event",b)._fire(b.data)};c.onerror=function(c){a.data("event",c).fire("close",b?"aborted":"error")};c.onclose=function(c){a.data("event",c).fire("close",b?"aborted":c.wasClean?"done":"error")}},send:function(a){c.send(a)},close:function(){b=!0;c.close()}}},httpbase:function(a, c){function b(){d.length?f(c.url,d.shift()):g=!1}var f,g,d=[];f=!c.crossDomain||e.support.corsable?function(a,d){var f=e.support.xhr();f.onreadystatechange=function(){4===f.readyState&&b()};f.open("POST",a);f.setRequestHeader("Content-Type","text/plain; charset=UTF-8");e.support.corsable&&(f.withCredentials=c.credentials);f.send("data="+d)}:window.XDomainRequest&&c.xdrURL&&c.xdrURL.call(a,"t")?function(d,e){var f=new window.XDomainRequest;f.onload=f.onerror=b;f.open("POST",c.xdrURL.call(a,d));f.send("data="+ e)}:function(a,c){var d=document.createElement("form");d.action=a;d.target="socket-"+ ++v;d.method="POST";d.enctype=d.encoding="text/plain";d.acceptCharset="UTF-8";d.style.display="none";d.innerHTML='<textarea name="data"></textarea><iframe name="'+d.target+'"></iframe>';d.firstChild.value=c;e.support.on(d.lastChild,"load",function(){document.body.removeChild(d);b()});document.body.appendChild(d);d.submit()};return{send:function(a){d.push(a);g||(g=!0,b())}}},sse:function(a,c){var b,f=window.EventSource; if(f){if(c.crossDomain)try{if(!e.support.corsable||!("withCredentials"in new f("about:blank")))return}catch(g){return}return e.support.extend(e.transports.httpbase(a,c),{open:function(){var d=a.data("url");b=!c.crossDomain?new f(d):new f(d,{withCredentials:c.credentials});b.onopen=function(b){a.data("event",b).fire("open")};b.onmessage=function(b){a.data("event",b)._fire(b.data)};b.onerror=function(c){b.close();a.data("event",c).fire("close","done")}},close:function(){b.close()}})}},stream:function(a){a.data("candidates").unshift("streamxhr", "streamxdr","streamiframe")},streamxhr:function(a,c){var b;if(!(e.support.browser.msie&&10>+e.support.browser.version||c.crossDomain&&!e.support.corsable))return e.support.extend(e.transports.httpbase(a,c),{open:function(){var f;b=e.support.xhr();b.onreadystatechange=function(){function c(){var d=a.data("index"),e=b.responseText.length;d?e>d&&a._fire(b.responseText.substring(d,e),!0):a.fire("open")._fire(b.responseText,!0);a.data("index",e)}3===b.readyState&&200===b.status?e.support.browser.opera&& !f?f=e.support.iterate(c):c():4===b.readyState&&(f&&f(),a.fire("close",200===b.status?"done":"error"))};b.open(c.method||"GET",a.data("url"));e.support.corsable&&(b.withCredentials=c.credentials);b.send(null)},close:function(){b.abort()}})},streamiframe:function(a,c){var b,f,g=window.ActiveXObject;if(g&&!c.crossDomain){try{new g("htmlfile")}catch(d){return}return e.support.extend(e.transports.httpbase(a,c),{open:function(){var d,m;b=new g("htmlfile");b.open();b.close();d=b.createElement("iframe"); d.src=a.data("url");b.body.appendChild(d);m=d.contentDocument||d.contentWindow.document;f=e.support.iterate(function(){function b(){var a=g.cloneNode(!0);a.appendChild(m.createTextNode("."));a=a.innerText;return a.substring(0,a.length-1)}var g;if(m.firstChild){c.initIframe&&c.initIframe.call(a,d);g=m.body.lastChild;if(!g)return a.fire("close","error"),!1;a.fire("open")._fire(b(),!0);g.innerText="";f=e.support.iterate(function(){var c=b();c&&(g.innerText="",a._fire(c,!0));if("complete"===m.readyState)return a.fire("close", "done"),!1});return!1}})},close:function(){f();b.execCommand("Stop")}})}},streamxdr:function(a,c){var b,f=window.XDomainRequest;if(f&&c.xdrURL&&c.xdrURL.call(a,"t"))return e.support.extend(e.transports.httpbase(a,c),{open:function(){var e=c.xdrURL.call(a,a.data("url"));a.data("url",e);b=new f;b.onprogress=function(){var c=a.data("index"),e=b.responseText.length;c?a._fire(b.responseText.substring(c,e),!0):a.fire("open")._fire(b.responseText,!0);a.data("index",e)};b.onerror=function(){a.fire("close", "error")};b.onload=function(){a.fire("close","done")};b.open(c.method||"GET",e);b.send()},close:function(){b.abort()}})},longpoll:function(a){a.data("candidates").unshift("longpollajax","longpollxdr","longpolljsonp")},longpollajax:function(a,c){var b,f,g=0;if(!c.crossDomain||e.support.corsable)return e.support.extend(e.transports.httpbase(a,c),{open:function(){function d(){var h=a.buildURL(!g?"open":"poll",{count:++g});a.data("url",h);b=e.support.xhr();b.onreadystatechange=function(){var c;!f&&4=== b.readyState&&(200===b.status?(c=b.responseText)||1===g?(1===g&&a.fire("open"),c&&a._fire(c),d()):a.fire("close","done"):a.fire("close","error"))};b.open(c.method||"GET",h);e.support.corsable&&(b.withCredentials=c.credentials);b.send(null)}c.longpollTest?d():setTimeout(function(){a.fire("open");d()},50)},close:function(){f=!0;b.abort()}})},longpollxdr:function(a,c){var b,f=0,g=window.XDomainRequest;if(g&&c.xdrURL&&c.xdrURL.call(a,"t"))return e.support.extend(e.transports.httpbase(a,c),{open:function(){function d(){var e= c.xdrURL.call(a,a.buildURL(!f?"open":"poll",{count:++f}));a.data("url",e);b=new g;b.onload=function(){var c=b.responseText;c||1===f?(1===f&&a.fire("open"),c&&a._fire(c),d()):a.fire("close","done")};b.onerror=function(){a.fire("close","error")};b.open(c.method||"GET",e);b.send()}c.longpollTest?d():setTimeout(function(){a.fire("open");d()},50)},close:function(){b.abort()}})},longpolljsonp:function(a,c){var b,f,g=0,d=B.pop()||"socket_"+ ++v;return e.support.extend(e.transports.httpbase(a,c),{open:function(){function e(){var c= a.buildURL(!g?"open":"poll",{callback:d,count:++g}),l=document.head||document.getElementsByTagName("head")[0]||document.documentElement;a.data("url",c);b=document.createElement("script");b.async=!0;b.src=c;b.clean=function(){b.clean=b.onerror=b.onload=b.onreadystatechange=null;b.parentNode&&b.parentNode.removeChild(b)};b.onload=b.onreadystatechange=function(){if(!b.readyState||/loaded|complete/.test(b.readyState))b.clean(),f?(f=!1,e()):1===g?(a.fire("open"),e()):a.fire("close","done")};b.onerror= function(){b.clean();a.fire("close","error")};l.insertBefore(b,l.firstChild)}window[d]=function(b){f=!0;1===g&&a.fire("open");a._fire(b)};a.one("close",function(){window[d]=function(){};B.push(d)});c.longpollTest?e():setTimeout(function(){a.fire("open");e()},50)},close:function(){b.clean&&b.clean()}})}};e.finalize=function(){var a,c;for(a in q)c=q[a],"closed"!==c.state()&&c.close(),delete q[a]};e.support.on(window,"unload",function(){w=!0;e.finalize()});e.support.on(window,"online",function(){var a, c;for(a in q)c=q[a],"waiting"===c.state()&&c.open()});e.support.on(window,"offline",function(){var a,c;for(a in q)c=q[a],"opened"===c.state()&&c.fire("close","error")});window.portal=e})(); \ No newline at end of file
diff --git a/client/src/com/vaadin/client/ApplicationConfiguration.java b/client/src/com/vaadin/client/ApplicationConfiguration.java
index 2291f21361..a6cc3cf531 100644
--- a/client/src/com/vaadin/client/ApplicationConfiguration.java
+++ b/client/src/com/vaadin/client/ApplicationConfiguration.java
@@ -36,6 +36,7 @@ import com.vaadin.client.metadata.NoDataException;
import com.vaadin.client.metadata.TypeData;
import com.vaadin.client.ui.UnknownComponentConnector;
import com.vaadin.shared.ApplicationConstants;
+import com.vaadin.shared.communication.PushMode;
import com.vaadin.shared.ui.ui.UIConstants;
public class ApplicationConfiguration implements EntryPoint {
@@ -201,6 +202,7 @@ public class ApplicationConfiguration implements EntryPoint {
private ErrorMessage authorizationError;
private ErrorMessage sessionExpiredError;
private int heartbeatInterval;
+ private PushMode pushMode;
private HashMap<Integer, String> unknownComponents;
@@ -304,6 +306,10 @@ public class ApplicationConfiguration implements EntryPoint {
return heartbeatInterval;
}
+ public PushMode getPushMode() {
+ return pushMode;
+ }
+
public JavaScriptObject getVersionInfoJSObject() {
return getJsoConfiguration(id).getVersionInfoJSObject();
}
@@ -357,6 +363,14 @@ public class ApplicationConfiguration implements EntryPoint {
heartbeatInterval = jsoConfiguration
.getConfigInteger("heartbeatInterval");
+ String pushMode = jsoConfiguration.getConfigString("pushMode");
+ if (pushMode != null) {
+ this.pushMode = Enum
+ .valueOf(PushMode.class, pushMode.toUpperCase());
+ } else {
+ this.pushMode = PushMode.DISABLED;
+ }
+
communicationError = jsoConfiguration.getConfigError("comErrMsg");
authorizationError = jsoConfiguration.getConfigError("authErrMsg");
sessionExpiredError = jsoConfiguration.getConfigError("sessExpMsg");
@@ -365,7 +379,6 @@ public class ApplicationConfiguration implements EntryPoint {
if (jsoConfiguration.getConfigBoolean("initPending") == Boolean.FALSE) {
setBrowserDetailsSent();
}
-
}
/**
diff --git a/client/src/com/vaadin/client/ApplicationConnection.java b/client/src/com/vaadin/client/ApplicationConnection.java
index d59abc892a..0341a9d5c4 100644
--- a/client/src/com/vaadin/client/ApplicationConnection.java
+++ b/client/src/com/vaadin/client/ApplicationConnection.java
@@ -66,6 +66,7 @@ import com.vaadin.client.communication.HasJavaScriptConnectorHelper;
import com.vaadin.client.communication.JavaScriptMethodInvocation;
import com.vaadin.client.communication.JsonDecoder;
import com.vaadin.client.communication.JsonEncoder;
+import com.vaadin.client.communication.PushConnection;
import com.vaadin.client.communication.RpcManager;
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.extensions.AbstractExtensionConnector;
@@ -88,6 +89,7 @@ import com.vaadin.shared.ApplicationConstants;
import com.vaadin.shared.Version;
import com.vaadin.shared.communication.LegacyChangeVariablesInvocation;
import com.vaadin.shared.communication.MethodInvocation;
+import com.vaadin.shared.communication.PushMode;
import com.vaadin.shared.communication.SharedState;
import com.vaadin.shared.ui.ui.UIConstants;
@@ -222,6 +224,8 @@ public class ApplicationConnection {
private final RpcManager rpcManager;
+ private PushConnection push;
+
/**
* If responseHandlingLocks contains any objects, response handling is
* suspended until the collection is empty or a timeout has occurred.
@@ -439,6 +443,8 @@ public class ApplicationConnection {
scheduleHeartbeat();
+ initializePush();
+
Window.addWindowClosingHandler(new ClosingHandler() {
@Override
public void onWindowClosing(ClosingEvent event) {
@@ -831,13 +837,16 @@ public class ApplicationConnection {
response.getText().length() - 1);
handleJSONText(jsonText, statusCode);
}
-
};
- try {
- doAjaxRequest(uri, payload, requestCallback);
- } catch (RequestException e) {
- VConsole.error(e);
- endRequest();
+ if (push != null) {
+ push.push(payload);
+ } else {
+ try {
+ doAjaxRequest(uri, payload, requestCallback);
+ } catch (RequestException e) {
+ VConsole.error(e);
+ endRequest();
+ }
}
}
@@ -848,7 +857,7 @@ public class ApplicationConnection {
* @param jsonText
* @param statusCode
*/
- private void handleJSONText(String jsonText, int statusCode) {
+ public void handleJSONText(String jsonText, int statusCode) {
final Date start = new Date();
final ValueMap json;
try {
@@ -952,7 +961,7 @@ public class ApplicationConnection {
* servicing the session so far. These values are always one request behind,
* since they cannot be measured before the request is finished.
*/
- private ValueMap serverTimingInfo;
+ public ValueMap serverTimingInfo;
static final int MAX_CSS_WAITS = 100;
@@ -1462,7 +1471,11 @@ public class ApplicationConnection {
+ jsonText.length() + " characters of JSON");
VConsole.log("Referenced paintables: " + connectorMap.size());
- endRequest();
+ if (meta == null || !meta.containsKey("async")) {
+ // End the request if the received message was a response,
+ // not sent asynchronously
+ endRequest();
+ }
if (Profiler.isEnabled()) {
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@@ -1473,7 +1486,6 @@ public class ApplicationConnection {
}
});
}
-
}
/**
@@ -3314,4 +3326,28 @@ public class ApplicationConnection {
return Util.getConnectorForElement(this, getUIConnector().getWidget(),
focusedElement);
}
+
+ private void initializePush() {
+ if (getConfiguration().getPushMode() != PushMode.DISABLED) {
+ push = GWT.create(PushConnection.class);
+ push.init(this);
+
+ final String pushUri = addGetParameters(
+ translateVaadinUri(ApplicationConstants.APP_PROTOCOL_PREFIX
+ + ApplicationConstants.PUSH_PATH + '/'),
+ UIConstants.UI_ID_PARAMETER + "="
+ + getConfiguration().getUIId());
+
+ Scheduler.get().scheduleDeferred(new Command() {
+ @Override
+ public void execute() {
+ push.connect(pushUri);
+ }
+ });
+ }
+ }
+
+ public void handlePushMessage(String message) {
+ handleJSONText(message, 200);
+ }
}
diff --git a/client/src/com/vaadin/client/communication/PushConnection.java b/client/src/com/vaadin/client/communication/PushConnection.java
new file mode 100644
index 0000000000..f87ddf430a
--- /dev/null
+++ b/client/src/com/vaadin/client/communication/PushConnection.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.client.communication;
+
+import java.util.ArrayList;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.vaadin.client.ApplicationConnection;
+import com.vaadin.client.VConsole;
+
+/**
+ * Represents the client-side endpoint of a bidirectional ("push") communication
+ * channel. Can be used to send UIDL request messages to the server and to
+ * receive UIDL messages from the server (either asynchronously or as a response
+ * to a UIDL request.) Delegates the UIDL handling to the
+ * {@link ApplicationConnection}.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class PushConnection {
+
+ private ApplicationConnection connection;
+
+ private JavaScriptObject socket;
+
+ private ArrayList<String> messageQueue = new ArrayList<String>();
+
+ private boolean connected = false;
+
+ private JavaScriptObject config = createConfig();
+
+ public PushConnection() {
+ }
+
+ /**
+ * Two-phase construction to allow using GWT.create()
+ *
+ * @param ac
+ * The ApplicationConnection
+ */
+ public void init(ApplicationConnection ac) {
+ this.connection = ac;
+ }
+
+ public void connect(String uri) {
+ VConsole.log("Establishing Atmosphere connection");
+ socket = doConnect(uri, getConfig());
+ }
+
+ public void push(String message) {
+ if (!connected) {
+ VConsole.log("Queuing Atmosphere message: " + message);
+ messageQueue.add(message);
+ } else {
+ VConsole.log("Pushing Atmosphere message: " + message);
+ doPush(socket, message);
+ }
+ }
+
+ protected JavaScriptObject getConfig() {
+ return config;
+ }
+
+ protected void onOpen() {
+ VConsole.log("Atmosphere connection established");
+ connected = true;
+ for (String message : messageQueue) {
+ push(message);
+ }
+ messageQueue.clear();
+ }
+
+ protected void onMessage(String message) {
+ if (message.startsWith("for(;;);")) {
+ VConsole.log("Received Atmosphere message: " + message);
+ // "for(;;);[{json}]" -> "{json}"
+ message = message.substring(9, message.length() - 1);
+ connection.handlePushMessage(message);
+ }
+ }
+
+ protected void onError() {
+ VConsole.error("Atmosphere connection failed!");
+ }
+
+ private static native JavaScriptObject createConfig()
+ /*-{
+ return {
+ transport: 'websocket',
+ fallbackTransport: 'streaming',
+ contentType: 'application/json; charset=UTF-8',
+ reconnectInterval: '5000',
+ trackMessageLength: true
+ };
+ }-*/;
+
+ private native JavaScriptObject doConnect(String uri,
+ JavaScriptObject config)
+ /*-{
+ var self = this;
+
+ config.url = uri;
+ config.onOpen = $entry(function(response) {
+ self.@com.vaadin.client.communication.PushConnection::onOpen()();
+ });
+ config.onMessage = $entry(function(response) {
+ self.@com.vaadin.client.communication.PushConnection::onMessage(*)(response.responseBody);
+ });
+ config.onError = $entry(function(response) {
+ self.@com.vaadin.client.communication.PushConnection::onError()();
+ });
+
+ return $wnd.atmosphere.subscribe(config);
+ }-*/;
+
+ private native void doPush(JavaScriptObject socket, String message)
+ /*-{
+ socket.push(message);
+ }-*/;
+}
diff --git a/server/ivy.xml b/server/ivy.xml
index d757e3a3cd..09e34fc075 100644
--- a/server/ivy.xml
+++ b/server/ivy.xml
@@ -49,6 +49,10 @@
<!-- Jsoup for BootstrapHandler -->
<dependency org="org.jsoup" name="jsoup" rev="1.6.3"
conf="build,ide,test -> default" />
+
+ <!-- Atmosphere -->
+ <dependency org="org.atmosphere" name="atmosphere-runtime" rev="1.0.12"
+ conf="build,ide,test -> default" />
<!-- TESTING DEPENDENCIES -->
diff --git a/server/src/com/vaadin/server/BootstrapHandler.java b/server/src/com/vaadin/server/BootstrapHandler.java
index 671279219e..d4f1ad308a 100644
--- a/server/src/com/vaadin/server/BootstrapHandler.java
+++ b/server/src/com/vaadin/server/BootstrapHandler.java
@@ -41,6 +41,7 @@ import org.jsoup.parser.Tag;
import com.vaadin.shared.ApplicationConstants;
import com.vaadin.shared.Version;
+import com.vaadin.shared.communication.PushMode;
import com.vaadin.ui.UI;
/**
@@ -337,8 +338,8 @@ public abstract class BootstrapHandler extends SynchronizedRequestHandler {
VaadinRequest request = context.getRequest();
VaadinService vaadinService = request.getService();
- String staticFileLocation = vaadinService
- .getStaticFileLocation(request);
+ String vaadinLocation = vaadinService.getStaticFileLocation(request)
+ + "/VAADIN/";
fragmentNodes
.add(new Element(Tag.valueOf("iframe"), "")
@@ -348,8 +349,17 @@ public abstract class BootstrapHandler extends SynchronizedRequestHandler {
"position:absolute;width:0;height:0;border:0;overflow:hidden")
.attr("src", "javascript:false"));
- String bootstrapLocation = staticFileLocation
- + "/VAADIN/vaadinBootstrap.js";
+ if (context.getSession().getPushMode() != PushMode.DISABLED) {
+ // Load client-side dependencies for push support
+ fragmentNodes.add(new Element(Tag.valueOf("script"), "").attr(
+ "type", "text/javascript").attr("src",
+ vaadinLocation + "portal.min.js"));
+ fragmentNodes.add(new Element(Tag.valueOf("script"), "").attr(
+ "type", "text/javascript").attr("src",
+ vaadinLocation + "atmosphere.min.js"));
+ }
+
+ String bootstrapLocation = vaadinLocation + "vaadinBootstrap.js";
fragmentNodes.add(new Element(Tag.valueOf("script"), "").attr("type",
"text/javascript").attr("src", bootstrapLocation));
Element mainScriptTag = new Element(Tag.valueOf("script"), "").attr(
@@ -477,6 +487,8 @@ public abstract class BootstrapHandler extends SynchronizedRequestHandler {
appConfig.put("heartbeatInterval", vaadinService
.getDeploymentConfiguration().getHeartbeatInterval());
+ appConfig.put("pushMode", session.getPushMode().toString());
+
String serviceUrl = getServiceUrl(context);
if (serviceUrl != null) {
appConfig.put(ApplicationConstants.SERVICE_URL, serviceUrl);
diff --git a/server/src/com/vaadin/server/Constants.java b/server/src/com/vaadin/server/Constants.java
index a9bc3e5b9e..d0f8507c94 100644
--- a/server/src/com/vaadin/server/Constants.java
+++ b/server/src/com/vaadin/server/Constants.java
@@ -47,6 +47,13 @@ public interface Constants {
+ "in web.xml. The default of 5min will be used.\n"
+ "===========================================================";
+ static final String WARNING_PUSH_MODE_NOT_RECOGNIZED = "\n"
+ + "===========================================================\n"
+ + "WARNING: pushMode has been set to an unrecognized value\n"
+ + "in web.xml. The permitted values are \"disabled\", \"manual\",\n"
+ + "and \"automatic\". The default of \"disabled\" will be used.\n"
+ + "===========================================================";
+
static final String WIDGETSET_MISMATCH_INFO = "\n"
+ "=================================================================\n"
+ "The widgetset in use does not seem to be built for the Vaadin\n"
@@ -63,6 +70,7 @@ public interface Constants {
static final String SERVLET_PARAMETER_RESOURCE_CACHE_TIME = "resourceCacheTime";
static final String SERVLET_PARAMETER_HEARTBEAT_INTERVAL = "heartbeatInterval";
static final String SERVLET_PARAMETER_CLOSE_IDLE_SESSIONS = "closeIdleSessions";
+ static final String SERVLET_PARAMETER_PUSH_MODE = "pushMode";
static final String SERVLET_PARAMETER_UI_PROVIDER = "UIProvider";
// Configurable parameter names
diff --git a/server/src/com/vaadin/server/DefaultDeploymentConfiguration.java b/server/src/com/vaadin/server/DefaultDeploymentConfiguration.java
index 5b0c3fe8d1..d11bd69997 100644
--- a/server/src/com/vaadin/server/DefaultDeploymentConfiguration.java
+++ b/server/src/com/vaadin/server/DefaultDeploymentConfiguration.java
@@ -19,6 +19,8 @@ package com.vaadin.server;
import java.util.Properties;
import java.util.logging.Logger;
+import com.vaadin.shared.communication.PushMode;
+
/**
* The default implementation of {@link DeploymentConfiguration} based on a base
* class for resolving system properties and a set of init parameters.
@@ -33,6 +35,7 @@ public class DefaultDeploymentConfiguration implements DeploymentConfiguration {
private int resourceCacheTime;
private int heartbeatInterval;
private boolean closeIdleSessions;
+ private PushMode pushMode;
private final Class<?> systemPropertyBaseClass;
/**
@@ -55,6 +58,7 @@ public class DefaultDeploymentConfiguration implements DeploymentConfiguration {
checkResourceCacheTime();
checkHeartbeatInterval();
checkCloseIdleSessions();
+ checkPushMode();
}
@Override
@@ -167,12 +171,32 @@ public class DefaultDeploymentConfiguration implements DeploymentConfiguration {
return heartbeatInterval;
}
+ /**
+ * {@inheritDoc}
+ * <p>
+ * The default value is false.
+ */
@Override
public boolean isCloseIdleSessions() {
return closeIdleSessions;
}
/**
+ * {@inheritDoc}
+ * <p>
+ * The default mode is {@link PushMode#DISABLED}.
+ */
+ @Override
+ public PushMode getPushMode() {
+ return pushMode;
+ }
+
+ @Override
+ public Properties getInitParameters() {
+ return initParameters;
+ }
+
+ /**
* Log a warning if Vaadin is not running in production mode.
*/
private void checkProductionMode() {
@@ -231,13 +255,19 @@ public class DefaultDeploymentConfiguration implements DeploymentConfiguration {
.equals("true");
}
- private Logger getLogger() {
- return Logger.getLogger(getClass().getName());
+ private void checkPushMode() {
+ String mode = getApplicationOrSystemProperty(
+ Constants.SERVLET_PARAMETER_PUSH_MODE,
+ PushMode.DISABLED.toString());
+ try {
+ pushMode = Enum.valueOf(PushMode.class, mode.toUpperCase());
+ } catch (IllegalArgumentException e) {
+ getLogger().warning(Constants.WARNING_PUSH_MODE_NOT_RECOGNIZED);
+ pushMode = PushMode.DISABLED;
+ }
}
- @Override
- public Properties getInitParameters() {
- return initParameters;
+ private Logger getLogger() {
+ return Logger.getLogger(getClass().getName());
}
-
}
diff --git a/server/src/com/vaadin/server/DeploymentConfiguration.java b/server/src/com/vaadin/server/DeploymentConfiguration.java
index bd4bc928f4..23edf8052a 100644
--- a/server/src/com/vaadin/server/DeploymentConfiguration.java
+++ b/server/src/com/vaadin/server/DeploymentConfiguration.java
@@ -19,6 +19,8 @@ package com.vaadin.server;
import java.io.Serializable;
import java.util.Properties;
+import com.vaadin.shared.communication.PushMode;
+
/**
* A collection of properties configured at deploy time as well as a way of
* accessing third party properties not explicitly supported by this class.
@@ -78,6 +80,14 @@ public interface DeploymentConfiguration extends Serializable {
public boolean isCloseIdleSessions();
/**
+ * Returns the mode of bidirectional ("push") client-server communication
+ * that should be used.
+ *
+ * @return The push mode in use.
+ */
+ public PushMode getPushMode();
+
+ /**
* Gets the properties configured for the deployment, e.g. as init
* parameters to the servlet or portlet.
*
diff --git a/server/src/com/vaadin/server/ServletPortletHelper.java b/server/src/com/vaadin/server/ServletPortletHelper.java
index baf697cae3..c14467a10e 100644
--- a/server/src/com/vaadin/server/ServletPortletHelper.java
+++ b/server/src/com/vaadin/server/ServletPortletHelper.java
@@ -123,6 +123,10 @@ public class ServletPortletHelper implements Serializable {
return hasPathPrefix(request, ApplicationConstants.HEARTBEAT_PATH + '/');
}
+ public static boolean isPushRequest(VaadinRequest request) {
+ return hasPathPrefix(request, ApplicationConstants.PUSH_PATH + '/');
+ }
+
public static void initDefaultUIProvider(VaadinSession session,
VaadinService vaadinService) throws ServiceException {
String uiProperty = vaadinService.getDeploymentConfiguration()
@@ -191,7 +195,7 @@ public class ServletPortletHelper implements Serializable {
* <li>{@link Locale#getDefault()}</li>
* </ol>
*/
- static Locale findLocale(Component component, VaadinSession session,
+ public static Locale findLocale(Component component, VaadinSession session,
VaadinRequest request) {
if (component == null) {
component = UI.getCurrent();
@@ -225,5 +229,4 @@ public class ServletPortletHelper implements Serializable {
return Locale.getDefault();
}
-
}
diff --git a/server/src/com/vaadin/server/VaadinService.java b/server/src/com/vaadin/server/VaadinService.java
index 3b088294e3..ceabaaf729 100644
--- a/server/src/com/vaadin/server/VaadinService.java
+++ b/server/src/com/vaadin/server/VaadinService.java
@@ -650,6 +650,7 @@ public abstract class VaadinService implements Serializable, Callback {
session.setLocale(locale);
session.setConfiguration(getDeploymentConfiguration());
session.setCommunicationManager(new LegacyCommunicationManager(session));
+ session.setPushMode(getDeploymentConfiguration().getPushMode());
ServletPortletHelper.initDefaultUIProvider(session, this);
onVaadinSessionStarted(request, session);
diff --git a/server/src/com/vaadin/server/VaadinServletService.java b/server/src/com/vaadin/server/VaadinServletService.java
index a12e2b47e2..ba78efa9bb 100644
--- a/server/src/com/vaadin/server/VaadinServletService.java
+++ b/server/src/com/vaadin/server/VaadinServletService.java
@@ -28,8 +28,10 @@ import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import com.vaadin.server.communication.PushRequestHandler;
import com.vaadin.server.communication.ServletBootstrapHandler;
import com.vaadin.server.communication.ServletUIInitHandler;
+import com.vaadin.shared.communication.PushMode;
import com.vaadin.ui.UI;
public class VaadinServletService extends VaadinService {
@@ -73,6 +75,9 @@ public class VaadinServletService extends VaadinService {
List<RequestHandler> handlers = super.createRequestHandlers();
handlers.add(0, new ServletBootstrapHandler());
handlers.add(new ServletUIInitHandler());
+ if (getDeploymentConfiguration().getPushMode() != PushMode.DISABLED) {
+ handlers.add(new PushRequestHandler(this));
+ }
return handlers;
}
diff --git a/server/src/com/vaadin/server/VaadinSession.java b/server/src/com/vaadin/server/VaadinSession.java
index 844b7ff674..029a384e70 100644
--- a/server/src/com/vaadin/server/VaadinSession.java
+++ b/server/src/com/vaadin/server/VaadinSession.java
@@ -39,6 +39,7 @@ import com.vaadin.data.util.converter.Converter;
import com.vaadin.data.util.converter.ConverterFactory;
import com.vaadin.data.util.converter.DefaultConverterFactory;
import com.vaadin.event.EventRouter;
+import com.vaadin.shared.communication.PushMode;
import com.vaadin.ui.AbstractField;
import com.vaadin.ui.Table;
import com.vaadin.ui.UI;
@@ -128,6 +129,8 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
private transient Lock lock;
+ private PushMode pushMode;
+
/**
* Create a new service session tied to a Vaadin service
*
@@ -806,11 +809,28 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
/**
* Unlocks this session. This method should always be used in a finally
* block after {@link #lock()} to ensure that the lock is always released.
+ * <p>
+ * If {@link #getPushMode() the push mode} is {@link PushMode#AUTOMATIC
+ * automatic}, pushes the changes in all UIs in this session to their
+ * respective clients.
*
- * @see #unlock()
+ * @see #lock()
+ * @see UI#push()
*/
public void unlock() {
- getLockInstance().unlock();
+ assert hasLock();
+ try {
+ if (getPushMode() == PushMode.AUTOMATIC
+ && ((ReentrantLock) getLockInstance()).getHoldCount() == 1) {
+ // Only push if the reentrant lock will actually be released by
+ // this unlock() invocation.
+ for (UI ui : getUIs()) {
+ ui.push();
+ }
+ }
+ } finally {
+ getLockInstance().unlock();
+ }
}
/**
@@ -1005,6 +1025,39 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
}
/**
+ * Returns the mode of bidirectional ("push") communication that is used in
+ * this session.
+ *
+ * @return The push mode.
+ */
+ public PushMode getPushMode() {
+ return pushMode;
+ }
+
+ /**
+ * Sets the mode of bidirectional ("push") communication that should be used
+ * in this session. Set once on session creation and cannot be changed
+ * afterwards.
+ *
+ * @param pushMode
+ * The push mode to use.
+ *
+ * @throws IllegalArgumentException
+ * if the argument is null.
+ * @throws IllegalStateException
+ * if the mode is already set.
+ */
+ public void setPushMode(PushMode pushMode) {
+ if (pushMode == null) {
+ throw new IllegalArgumentException("Push mode cannot be null");
+ }
+ if (this.pushMode != null) {
+ throw new IllegalStateException("Push mode already set");
+ }
+ this.pushMode = pushMode;
+ }
+
+ /**
* Sets this session to be closed and all UI state to be discarded at the
* end of the current request, or at the end of the next request if there is
* no ongoing one.
diff --git a/server/src/com/vaadin/server/communication/MetadataWriter.java b/server/src/com/vaadin/server/communication/MetadataWriter.java
index 7119e0ffeb..1a3f0e946a 100644
--- a/server/src/com/vaadin/server/communication/MetadataWriter.java
+++ b/server/src/com/vaadin/server/communication/MetadataWriter.java
@@ -51,6 +51,9 @@ public class MetadataWriter implements Serializable {
* @param analyzeLayouts
* Whether detected layout problems should be reported in client
* and server console.
+ * @param async
+ * True if this message is sent by the server asynchronously,
+ * false if it is a response to a client message.
* @param hilightedConnector
* The connector that should be highlighted on the client or null
* if none.
@@ -62,8 +65,9 @@ public class MetadataWriter implements Serializable {
*
*/
public void write(UI ui, Writer writer, boolean repaintAll,
- boolean analyzeLayouts, ClientConnector hilightedConnector,
- SystemMessages messages) throws IOException {
+ boolean analyzeLayouts, boolean async,
+ ClientConnector hilightedConnector, SystemMessages messages)
+ throws IOException {
List<InvalidLayout> invalidComponentRelativeSizes = null;
@@ -112,6 +116,13 @@ public class MetadataWriter implements Serializable {
}
}
+ if (async) {
+ if (metaOpen) {
+ writer.write(", ");
+ }
+ writer.write("\"async\":true");
+ }
+
// meta instruction for client to enable auto-forward to
// sessionExpiredURL after timer expires.
if (messages != null && messages.getSessionExpiredMessage() == null
diff --git a/server/src/com/vaadin/server/communication/PushConnection.java b/server/src/com/vaadin/server/communication/PushConnection.java
new file mode 100644
index 0000000000..2db9d42763
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/PushConnection.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server.communication;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.io.StringWriter;
+import java.io.Writer;
+
+import org.atmosphere.cpr.AtmosphereResource;
+import org.json.JSONException;
+
+import com.vaadin.ui.UI;
+
+/**
+ * Represents a bidirectional ("push") connection between a single UI and its
+ * client-side.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class PushConnection implements Serializable {
+
+ private UI ui;
+ private boolean pending = true;
+ private AtmosphereResource resource;
+
+ public PushConnection(UI ui) {
+ this.ui = ui;
+ }
+
+ /**
+ * Pushes pending state changes and client RPC calls to the client. It is
+ * NOT safe to invoke this method if not holding the session lock.
+ * <p>
+ * This is internal API; please use {@link UI#push()} instead.
+ */
+ public void push() {
+ if (!isConnected()) {
+ // Not currently connected; defer until connection established
+ setPending(true);
+ } else {
+ try {
+ push(true);
+ } catch (IOException e) {
+ // TODO Error handling
+ throw new RuntimeException("Push failed", e);
+ }
+ }
+ }
+
+ /**
+ * Pushes pending state changes and client RPC calls to the client.
+ *
+ * @param async
+ * True if this push asynchronously originates from the server,
+ * false if it is a response to a client request.
+ * @throws IOException
+ */
+ protected void push(boolean async) throws IOException {
+ Writer writer = new StringWriter();
+ try {
+ new UidlWriter().write(getUI(), writer, false, false, async);
+ } catch (JSONException e) {
+ throw new IOException("Error writing UIDL", e);
+ }
+ // "Broadcast" the changes to the single client only
+ getResource().getBroadcaster().broadcast(writer.toString(),
+ getResource());
+ }
+
+ /**
+ * Associates this connection with the given AtmosphereResource. If there is
+ * a push pending, commits it.
+ *
+ * @param resource
+ * The AtmosphereResource representing the push channel.
+ * @throws IOException
+ */
+ protected void connect(AtmosphereResource resource) throws IOException {
+ this.resource = resource;
+ if (isPending()) {
+ push(true);
+ setPending(false);
+ }
+ }
+
+ /**
+ * Returns whether this connection is currently open.
+ */
+ protected boolean isConnected() {
+ return resource != null
+ && resource.getBroadcaster().getAtmosphereResources()
+ .contains(resource);
+ }
+
+ /**
+ * Marks that changes in the UI should be pushed as soon as a connection is
+ * established.
+ */
+ protected void setPending(boolean pending) {
+ this.pending = pending;
+ }
+
+ /**
+ * @return Whether the UI should be pushed as soon as a connection opens.
+ */
+ protected boolean isPending() {
+ return pending;
+ }
+
+ /**
+ * @return the UI associated with this connection.
+ */
+ protected UI getUI() {
+ return ui;
+ }
+
+ /**
+ * @return The AtmosphereResource associated with this connection or null if
+ * connection not open.
+ */
+ protected AtmosphereResource getResource() {
+ return resource;
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/PushHandler.java b/server/src/com/vaadin/server/communication/PushHandler.java
new file mode 100644
index 0000000000..39481db46a
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/PushHandler.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server.communication;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.atmosphere.cpr.AtmosphereHandler;
+import org.atmosphere.cpr.AtmosphereRequest;
+import org.atmosphere.cpr.AtmosphereResource;
+import org.atmosphere.cpr.AtmosphereResourceEvent;
+import org.json.JSONException;
+
+import com.vaadin.server.LegacyCommunicationManager.InvalidUIDLSecurityKeyException;
+import com.vaadin.server.ServiceException;
+import com.vaadin.server.SessionExpiredException;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.server.VaadinService;
+import com.vaadin.server.VaadinSession;
+import com.vaadin.ui.UI;
+
+/**
+ * Establishes bidirectional ("push") communication channels
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class PushHandler implements AtmosphereHandler {
+
+ private VaadinService service;
+
+ public PushHandler(VaadinService service) {
+ this.service = service;
+ }
+
+ @Override
+ public void onRequest(AtmosphereResource resource) {
+
+ AtmosphereRequest req = resource.getRequest();
+ VaadinRequest vaadinRequest = getVaadinRequest(req);
+
+ VaadinSession session;
+ try {
+ session = service.findVaadinSession(vaadinRequest);
+ } catch (ServiceException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ return;
+ } catch (SessionExpiredException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ return;
+ }
+
+ session.lock();
+ try {
+ UI ui = service.findUI(vaadinRequest);
+ if (ui == null) {
+ throw new RuntimeException("UI not found!");
+ }
+ PushConnection connection = ui.getPushConnection();
+
+ if (req.getMethod().equalsIgnoreCase("GET")) {
+ /*
+ * We received a request to establish a push channel for a UI.
+ * Associate the AtmosphereResource with the UI and leave the
+ * connection open by calling resource.suspend(). If there is a
+ * pending push, send it now.
+ */
+ getLogger().log(Level.FINER,
+ "New push connection with transport {}",
+ resource.transport());
+ resource.suspend();
+
+ connection.connect(resource);
+ } else if (req.getMethod().equalsIgnoreCase("POST")) {
+ /*
+ * We received a UIDL request through Atmosphere. If the push
+ * channel is bidirectional (websockets), the request was sent
+ * via the same channel. Otherwise, the client used a separate
+ * AJAX request. Handle the request and send changed UI state
+ * via the push channel (we do not respond to the request
+ * directly.)
+ */
+ new ServerRpcHandler().handleRpc(ui, req.getReader(),
+ vaadinRequest);
+ connection.push(false);
+ }
+ } catch (InvalidUIDLSecurityKeyException e) {
+ // TODO Error handling
+ e.printStackTrace();
+ } catch (JSONException e) {
+ // TODO Error handling
+ e.printStackTrace();
+ } catch (IOException e) {
+ // TODO Error handling
+ e.printStackTrace();
+ } finally {
+ session.unlock();
+ }
+ }
+
+ @Override
+ public void onStateChange(AtmosphereResourceEvent event) throws IOException {
+ AtmosphereResource resource = event.getResource();
+
+ String id = resource.uuid();
+ if (event.isCancelled()) {
+ // The client closed the connection.
+ // TODO Do some cleanup
+ getLogger().log(Level.FINER, "Connection closed for resource {}",
+ id);
+ } else if (event.isResuming()) {
+ // A connection that was suspended earlier was resumed (committed to
+ // the client.) Should only happen if the transport is JSONP or
+ // long-polling.
+ getLogger()
+ .log(Level.FINER, "Resuming request for resource {}", id);
+ } else {
+ // A message was broadcast to this resource and should be sent to
+ // the client. We don't do any actual broadcasting, in the sense of
+ // sending to multiple recipients; any UIDL message is specific to a
+ // single client.
+ getLogger().log(Level.FINER, "Writing message to resource {}", id);
+
+ resource.getResponse().setContentType(
+ "application/json; charset=UTF-8");
+ Writer writer = resource.getResponse().getWriter();
+ writer.write("for(;;);[{" + event.getMessage() + "}]");
+
+ switch (resource.transport()) {
+ case SSE:
+ case WEBSOCKET:
+ break;
+ case STREAMING:
+ writer.flush();
+ break;
+ case JSONP:
+ case LONG_POLLING:
+ resource.resume();
+ break;
+ default:
+ getLogger().log(Level.SEVERE, "Unknown transport {}",
+ resource.transport());
+ }
+ }
+ }
+
+ @Override
+ public void destroy() {
+ }
+
+ private VaadinRequest getVaadinRequest(AtmosphereRequest req) {
+ while (req.getRequest() instanceof AtmosphereRequest) {
+ req = (AtmosphereRequest) req.getRequest();
+ }
+ if (req.getRequest() instanceof VaadinRequest) {
+ return (VaadinRequest) req.getRequest();
+ } else {
+ throw new IllegalArgumentException(
+ "Request does not wrap VaadinRequest");
+ }
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(PushHandler.class.getName());
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/PushRequestHandler.java b/server/src/com/vaadin/server/communication/PushRequestHandler.java
new file mode 100644
index 0000000000..10ef16e11c
--- /dev/null
+++ b/server/src/com/vaadin/server/communication/PushRequestHandler.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.server.communication;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+
+import org.atmosphere.client.TrackMessageSizeInterceptor;
+import org.atmosphere.cpr.AtmosphereFramework;
+import org.atmosphere.cpr.AtmosphereRequest;
+import org.atmosphere.cpr.AtmosphereResponse;
+
+import com.vaadin.server.RequestHandler;
+import com.vaadin.server.ServletPortletHelper;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.server.VaadinResponse;
+import com.vaadin.server.VaadinService;
+import com.vaadin.server.VaadinServletRequest;
+import com.vaadin.server.VaadinServletResponse;
+import com.vaadin.server.VaadinSession;
+
+/**
+ * Handles requests to open a push (bidirectional) communication channel between
+ * the client and the server. After the initial request, communication through
+ * the push channel is managed by {@link PushHandler}.
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public class PushRequestHandler implements RequestHandler {
+
+ private AtmosphereFramework atmosphere;
+ private PushHandler pushHandler;
+
+ public PushRequestHandler(VaadinService service) {
+
+ atmosphere = new AtmosphereFramework();
+
+ pushHandler = new PushHandler(service);
+ atmosphere.addAtmosphereHandler("/*", pushHandler);
+ atmosphere
+ .addInitParameter("org.atmosphere.cpr.sessionSupport", "true");
+
+ // Required to ensure the client-side knows at which points to split the
+ // message stream into individual messages when using certain transports
+ atmosphere.interceptor(new TrackMessageSizeInterceptor());
+
+ atmosphere.init();
+ }
+
+ @Override
+ public boolean handleRequest(VaadinSession session, VaadinRequest request,
+ VaadinResponse response) throws IOException {
+
+ if (!ServletPortletHelper.isPushRequest(request)) {
+ return false;
+ }
+
+ if (request instanceof VaadinServletRequest) {
+ try {
+ atmosphere.doCometSupport(AtmosphereRequest
+ .wrap((VaadinServletRequest) request),
+ AtmosphereResponse
+ .wrap((VaadinServletResponse) response));
+ } catch (ServletException e) {
+ // TODO PUSH decide how to handle
+ throw new RuntimeException(e);
+ }
+ } else {
+ throw new IllegalArgumentException(
+ "Portlets not currently supported");
+ }
+
+ return true;
+ }
+
+ public void destroy() {
+ atmosphere.destroy();
+ }
+}
diff --git a/server/src/com/vaadin/server/communication/UIInitHandler.java b/server/src/com/vaadin/server/communication/UIInitHandler.java
index c3e7119d3f..8275ea3efd 100644
--- a/server/src/com/vaadin/server/communication/UIInitHandler.java
+++ b/server/src/com/vaadin/server/communication/UIInitHandler.java
@@ -37,6 +37,7 @@ import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinResponse;
import com.vaadin.server.VaadinService;
import com.vaadin.server.VaadinSession;
+import com.vaadin.shared.communication.PushMode;
import com.vaadin.shared.ui.ui.UIConstants;
import com.vaadin.ui.UI;
@@ -205,6 +206,10 @@ public abstract class UIInitHandler extends SynchronizedRequestHandler {
// Set thread local here so it is available in init
UI.setCurrent(ui);
+ if (session.getPushMode() != PushMode.DISABLED) {
+ ui.setPushConnection(new PushConnection(ui));
+ }
+
ui.doInit(request, uiId.intValue());
session.addUI(ui);
@@ -263,7 +268,7 @@ public abstract class UIInitHandler extends SynchronizedRequestHandler {
writer.write(uI.getSession().getCommunicationManager()
.getSecurityKeyUIDL(request));
}
- new UidlWriter().write(uI, writer, true, false);
+ new UidlWriter().write(uI, writer, true, false, false);
writer.write("}");
String initialUIDL = writer.toString();
diff --git a/server/src/com/vaadin/server/communication/UidlRequestHandler.java b/server/src/com/vaadin/server/communication/UidlRequestHandler.java
index 0de9029063..32f9df3eff 100644
--- a/server/src/com/vaadin/server/communication/UidlRequestHandler.java
+++ b/server/src/com/vaadin/server/communication/UidlRequestHandler.java
@@ -169,7 +169,7 @@ public class UidlRequestHandler extends SynchronizedRequestHandler {
.getSecurityKeyUIDL(request));
}
- new UidlWriter().write(ui, writer, repaintAll, analyzeLayouts);
+ new UidlWriter().write(ui, writer, repaintAll, analyzeLayouts, false);
closeJsonMessage(writer);
}
diff --git a/server/src/com/vaadin/server/communication/UidlWriter.java b/server/src/com/vaadin/server/communication/UidlWriter.java
index 81bbb91649..79ae8af07e 100644
--- a/server/src/com/vaadin/server/communication/UidlWriter.java
+++ b/server/src/com/vaadin/server/communication/UidlWriter.java
@@ -62,14 +62,18 @@ public class UidlWriter implements Serializable {
* Whether the client should re-render the whole UI.
* @param analyzeLayouts
* Whether detected layout problems should be logged.
+ * @param async
+ * True if this message is sent by the server asynchronously,
+ * false if it is a response to a client message.
+ *
* @throws IOException
* If the writing fails.
* @throws JSONException
* If the JSON serialization fails.
*/
public void write(UI ui, Writer writer, boolean repaintAll,
- boolean analyzeLayouts) throws IOException, JSONException {
-
+ boolean analyzeLayouts, boolean async) throws IOException,
+ JSONException {
ArrayList<ClientConnector> dirtyVisibleConnectors = ui
.getConnectorTracker().getDirtyVisibleConnectors();
VaadinSession session = ui.getSession();
@@ -153,7 +157,7 @@ public class UidlWriter implements Serializable {
.getSystemMessages(ui.getLocale(), null);
// TODO hilightedConnector
new MetadataWriter().write(ui, writer, repaintAll, analyzeLayouts,
- null, messages);
+ async, null, messages);
writer.write(", ");
writer.write("\"resources\" : ");
@@ -289,8 +293,6 @@ public class UidlWriter implements Serializable {
assert (uiConnectorTracker.getDirtyConnectors().isEmpty()) : "Connectors have been marked as dirty during the end of the paint phase. This is most certainly not intended.";
writePerformanceData(ui, writer);
- } catch (IOException ex) {
- throw new RuntimeException(ex);
} finally {
uiConnectorTracker.setWritingResponse(false);
}
diff --git a/server/src/com/vaadin/ui/UI.java b/server/src/com/vaadin/ui/UI.java
index a20c2b2087..162d072222 100644
--- a/server/src/com/vaadin/ui/UI.java
+++ b/server/src/com/vaadin/ui/UI.java
@@ -37,8 +37,10 @@ import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinService;
import com.vaadin.server.VaadinServlet;
import com.vaadin.server.VaadinSession;
+import com.vaadin.server.communication.PushConnection;
import com.vaadin.shared.EventId;
import com.vaadin.shared.MouseEventDetails;
+import com.vaadin.shared.communication.PushMode;
import com.vaadin.shared.ui.ui.ScrollClientRpc;
import com.vaadin.shared.ui.ui.UIConstants;
import com.vaadin.shared.ui.ui.UIServerRpc;
@@ -470,6 +472,8 @@ public abstract class UI extends AbstractSingleComponentContainer implements
private Navigator navigator;
+ private PushConnection pushConnection = new PushConnection(this);
+
/**
* This method is used by Component.Focusable objects to request focus to
* themselves. Focus renders must be handled at window level (instead of
@@ -1118,4 +1122,48 @@ public abstract class UI extends AbstractSingleComponentContainer implements
return loadingIndicator;
}
+ /**
+ * Pushes the pending changes and client RPC invocations of this UI to the
+ * client-side.
+ * <p>
+ * As with all UI methods, it is not safe to call push() without holding the
+ * {@link VaadinSession#lock() session lock}.
+ *
+ * @throws IllegalStateException
+ * if push is disabled.
+ * @throws UIDetachedException
+ * if this UI is not attached to a session.
+ *
+ * @see VaadinSession#getPushMode()
+ *
+ * @since 7.1
+ */
+ public void push() {
+ VaadinSession session = getSession();
+ if (session != null) {
+ if (session.getPushMode() == PushMode.DISABLED) {
+ throw new IllegalStateException("Push not enabled");
+ }
+ assert pushConnection != null;
+ pushConnection.push();
+ } else {
+ throw new UIDetachedException("Trying to push a detached UI");
+ }
+ }
+
+ /**
+ * Returns the internal push connection object used by this UI. This method
+ * should only be called by the framework.
+ */
+ public PushConnection getPushConnection() {
+ return pushConnection;
+ }
+
+ /**
+ * Sets the internal push connection object used by this UI. This method
+ * should only be called by the framework.
+ */
+ public void setPushConnection(PushConnection connection) {
+ pushConnection = connection;
+ }
}
diff --git a/shared/src/com/vaadin/shared/ApplicationConstants.java b/shared/src/com/vaadin/shared/ApplicationConstants.java
index 220679e69c..5f23a3dc38 100644
--- a/shared/src/com/vaadin/shared/ApplicationConstants.java
+++ b/shared/src/com/vaadin/shared/ApplicationConstants.java
@@ -28,6 +28,8 @@ public class ApplicationConstants implements Serializable {
public static final String HEARTBEAT_PATH = "HEARTBEAT";
+ public static final String PUSH_PATH = "PUSH";
+
public static final String PUBLISHED_FILE_PATH = APP_PATH + '/'
+ "PUBLISHED";
diff --git a/shared/src/com/vaadin/shared/communication/PushMode.java b/shared/src/com/vaadin/shared/communication/PushMode.java
new file mode 100644
index 0000000000..f7414a89ea
--- /dev/null
+++ b/shared/src/com/vaadin/shared/communication/PushMode.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2000-2013 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.shared.communication;
+
+/**
+ * The mode of bidirectional ("push") communication that is in use.
+ *
+ * @see com.vaadin.server.DeploymentConfiguration#getPushMode()
+ *
+ * @author Vaadin Ltd
+ * @since 7.1
+ */
+public enum PushMode {
+ /**
+ * Push is disabled. Regular AJAX requests are used to communicate between
+ * the client and the server. Asynchronous messages from the server are not
+ * possible. {@link com.vaadin.ui.UI#push() ui.push()} throws
+ * IllegalStateException.
+ * <p>
+ * This is the default mode unless
+ * {@link com.vaadin.server.DeploymentConfiguration#getPushMode()
+ * configured} otherwise.
+ */
+ DISABLED,
+
+ /**
+ * Push is enabled. A bidirectional channel is established between the
+ * client and server and used to communicate state changes and RPC
+ * invocations. The client is not automatically updated if the server-side
+ * state is asynchronously changed; {@link com.vaadin.ui.UI#push()
+ * ui.push()} must be explicitly called.
+ */
+ MANUAL,
+
+ /**
+ * Push is enabled. Like {@link #MANUAL}, but asynchronous changes to the
+ * server-side state are automatically pushed to the client once the session
+ * lock is released.
+ */
+ AUTOMATIC;
+}
diff --git a/uitest/ivy.xml b/uitest/ivy.xml
index 4196cca4da..55f682e14e 100644
--- a/uitest/ivy.xml
+++ b/uitest/ivy.xml
@@ -48,16 +48,18 @@
<!-- Newest Jetty does not work with Ivy currently (orbit -> jar
mapping problem) -->
<dependency org="org.eclipse.jetty" name="jetty-server"
- rev="7.4.5.v20110725" conf="build, ide, jetty-run->default" />
+ rev="7.5.0.v20110901" conf="build, ide, jetty-run->default" />
<!-- jetty-servlets needed in .war by ProxyTest, but not by jetty-runner -->
<dependency org="org.eclipse.jetty" name="jetty-servlets"
- rev="7.4.5.v20110725" conf="build, ide->default" />
+ rev="7.5.0.v20110901" conf="build, ide->default" />
+ <dependency org="org.eclipse.jetty" name="jetty-websocket"
+ rev="7.5.0.v20110901" conf="build, ide->default" />
<!-- <dependency org="org.mortbay.jetty" name="jetty-util" -->
<!-- rev="8.1.5.v20120716" conf="build,ide,jetty-run->default" /> -->
<dependency org="org.eclipse.jetty" name="jetty-webapp"
- rev="7.4.5.v20110725" conf="build, ide,jetty-run->default" />
+ rev="7.5.0.v20110901" conf="build, ide,jetty-run->default" />
<dependency org="org.mortbay.jetty" name="jetty-runner"
- rev="7.4.5.v20110725" conf="jetty-run->default" />
+ rev="7.5.0.v20110901" conf="jetty-run->default" />
<dependency org="junit" name="junit" rev="4.5"
conf="build,ide -> default" />