This is an automated email from the ASF dual-hosted git repository.
nfilotto pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-karaf.git
The following commit(s) were added to refs/heads/main by this push:
new dc57a0a4 Ref #468: Add camel-cometd integration test (#469)
dc57a0a4 is described below
commit dc57a0a44291bb440ba225340a088163f7e09388
Author: François de Parscau <[email protected]>
AuthorDate: Wed Jul 31 09:26:29 2024 +0200
Ref #468: Add camel-cometd integration test (#469)
---
features/src/main/feature/camel-features.xml | 2 +
tests/features/camel-cometd/pom.xml | 81 +
.../main/resources/OSGI-INF/blueprint/route.xml | 43 +
.../apache/karaf/camel/itest/CamelCometdITest.java | 69 +
.../src/test/resources/webapp/authenticated.html | 65 +
.../src/test/resources/webapp/dojo/dojo/dojo.js | 30 +
.../test/resources/webapp/dojo/dojo/io/iframe.js | 250 ++
.../test/resources/webapp/dojo/dojo/io/script.js | 135 ++
.../src/test/resources/webapp/dojo/dojox/cometd.js | 115 +
.../webapp/dojo/dojox/cometd.js.uncompressed.js | 16 +
.../test/resources/webapp/dojo/dojox/cometd.xd.js | 16 +
.../webapp/dojo/dojox/cometd.xd.js.uncompressed.js | 16 +
.../test/resources/webapp/dojo/dojox/cometd/ack.js | 27 +
.../resources/webapp/dojo/dojox/cometd/reload.js | 34 +
.../webapp/dojo/dojox/cometd/timestamp.js | 26 +
.../resources/webapp/dojo/dojox/cometd/timesync.js | 26 +
.../src/test/resources/webapp/dojo/org/cometd.js | 2427 ++++++++++++++++++++
.../src/test/resources/webapp/index.html | 49 +
.../src/test/resources/webapp/inout.html | 58 +
tests/features/pom.xml | 1 +
20 files changed, 3486 insertions(+)
diff --git a/features/src/main/feature/camel-features.xml
b/features/src/main/feature/camel-features.xml
index df294df1..5cada3a7 100644
--- a/features/src/main/feature/camel-features.xml
+++ b/features/src/main/feature/camel-features.xml
@@ -808,10 +808,12 @@
<bundle
dependency='true'>mvn:org.eclipse.jetty.ee10/jetty-ee10-servlets/${jetty-version}</bundle>
<bundle
dependency='true'>mvn:org.cometd.java/cometd-java-server-common/${cometd-java-server-version}</bundle>
<bundle
dependency='true'>mvn:org.cometd.java/cometd-java-server-http-jakarta/${cometd-java-server-version}</bundle>
+ <bundle
dependency='true'>mvn:org.cometd.java/cometd-java-server-websocket-jetty/${cometd-java-server-version}</bundle>
<bundle
dependency='true'>mvn:org.cometd.java/cometd-java-common/${cometd-java-server-version}</bundle>
<bundle
dependency='true'>mvn:org.cometd.java/cometd-java-api-server/${cometd-java-server-version}</bundle>
<bundle
dependency='true'>mvn:org.cometd.java/cometd-java-api-common/${cometd-java-server-version}</bundle>
<bundle
dependency='true'>mvn:org.cometd.java/cometd-java-api-client/${cometd-java-server-version}</bundle>
+ <bundle
dependency="true">mvn:org.eclipse.jetty/jetty-util-ajax/${jetty-version}</bundle>
<bundle>mvn:org.apache.camel.karaf/camel-cometd/${project.version}</bundle>
</feature>
<feature name='camel-consul' version='${project.version}' start-level='50'>
diff --git a/tests/features/camel-cometd/pom.xml
b/tests/features/camel-cometd/pom.xml
new file mode 100644
index 00000000..397d35db
--- /dev/null
+++ b/tests/features/camel-cometd/pom.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.camel.karaf</groupId>
+ <artifactId>camel-karaf-features-test</artifactId>
+ <version>4.7.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>camel-cometd-test</artifactId>
+ <name>Apache Camel :: Karaf :: Tests :: Features :: Cometd</name>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <inherited>true</inherited>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Export-Package>
+
org.apache.karaf.camel.test;version=${project.version}
+ </Export-Package>
+ <Import-Package>
+ !org.apache.karaf.camel.test,
+ org.cometd.server.http,
+ org.eclipse.jetty.ee10.servlet,
+
org.apache.camel*;${camel-osgi-import-camel-version},
+ *
+ </Import-Package>
+ <Fragment-Host>
+ camel-integration-test
+ </Fragment-Host>
+ </instructions>
+ </configuration>
+ <executions>
+ <execution>
+ <id>cleanVersions</id>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>cleanVersions</goal>
+ </goals>
+ <configuration>
+ <versions>
+
<karaf.osgi.version>${project.version}</karaf.osgi.version>
+ </versions>
+ </configuration>
+ </execution>
+ <execution>
+ <id>bundle</id>
+ <phase>package</phase>
+ <goals>
+ <goal>bundle</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
\ No newline at end of file
diff --git
a/tests/features/camel-cometd/src/main/resources/OSGI-INF/blueprint/route.xml
b/tests/features/camel-cometd/src/main/resources/OSGI-INF/blueprint/route.xml
new file mode 100644
index 00000000..f7fd71e4
--- /dev/null
+++
b/tests/features/camel-cometd/src/main/resources/OSGI-INF/blueprint/route.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+-->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+
xmlns:ext="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0"
+ xsi:schemaLocation="
+ http://www.osgi.org/xmlns/blueprint/v1.0.0
https://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
+ http://camel.apache.org/schema/blueprint
http://camel.apache.org/schema/blueprint/camel-blueprint.xsd">
+
+ <camelContext id="camelContext"
xmlns="http://camel.apache.org/schema/blueprint">
+ <route id="cometdRoute">
+ <from uri="direct:camel-cometd-test"/>
+ <log message="start producer"/>
+ <to
uri="cometd://127.0.0.1:{{cometd.port}}/service/test?baseResource=file:{{project.target}}/test-classes/webapp&timeout=240000&interval=0&maxInterval=30000&multiFrameInterval=1500&jsonCommented=true&logLevel=2"
/>
+ <log message="message sent"/>
+ </route>
+
+ <route id="cometdRoute2">
+ <from
uri="cometd://127.0.0.1:{{cometd.port}}/service/test?baseResource=file:{{project.target}}/test-classes/webapp&timeout=240000&interval=0&maxInterval=30000&multiFrameInterval=1500&jsonCommented=true&logLevel=2"
/>
+ <log message="message received"/>
+ <setBody>
+ <constant>Cometd message received</constant>
+ </setBody>
+ <to uri="mock:camel-cometd-test"/>
+ </route>
+ </camelContext>
+</blueprint>
\ No newline at end of file
diff --git
a/tests/features/camel-cometd/src/test/java/org/apache/karaf/camel/itest/CamelCometdITest.java
b/tests/features/camel-cometd/src/test/java/org/apache/karaf/camel/itest/CamelCometdITest.java
new file mode 100644
index 00000000..2f677252
--- /dev/null
+++
b/tests/features/camel-cometd/src/test/java/org/apache/karaf/camel/itest/CamelCometdITest.java
@@ -0,0 +1,69 @@
+/*
+ * 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 org.apache.karaf.camel.itest;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+import org.apache.camel.component.mock.MockEndpoint;
+import
org.apache.karaf.camel.itests.AbstractCamelSingleFeatureResultMockBasedRouteITest;
+import org.apache.karaf.camel.itests.AvailablePortProvider;
+import org.apache.karaf.camel.itests.CamelKarafTestHint;
+import org.apache.karaf.camel.itests.PaxExamWithExternalResource;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
+import org.ops4j.pax.exam.spi.reactors.PerClass;
+
+@CamelKarafTestHint(
+ externalResourceProvider =
CamelCometdITest.ExternalResourceProviders.class,
+ isBlueprintTest = true
+)
+@RunWith(PaxExamWithExternalResource.class)
+@ExamReactorStrategy(PerClass.class)
+public class CamelCometdITest extends
AbstractCamelSingleFeatureResultMockBasedRouteITest {
+
+ @Override
+ public void configureMock(MockEndpoint mock) {
+ mock.expectedBodiesReceived("Cometd message received");
+ }
+
+ //override to install the test bundle without starting it as it is a
Fragment
+ @Override
+ protected List<String> installRequiredBundles() throws Exception {
+ String testBundleName = getTestBundleName();
+ String testBundleVersion = getTestBundleVersion();
+ if (testBundleVersion == null) {
+ throw new IllegalArgumentException("The system property
project.version must be set to the version of the " +
+ "test bundle to install or the method getTestBundleVersion
must be overridden to provide the version");
+ }
+ Path bundlePath = Paths.get("%s/%s-%s.jar".formatted(getBaseDir(),
testBundleName, testBundleVersion));
+ installBundle(bundlePath.toUri().toString(), false);
+ //refresh the host bundle to make the test fragment available
+ findBundleByName("camel-integration-test").update();
+ return List.of(testBundleName);
+ }
+
+ @Test
+ public void testResultMock() throws Exception {
+ assertMockEndpointsSatisfied();
+ }
+
+ public static final class ExternalResourceProviders {
+ public static AvailablePortProvider createAvailablePortProvider() {
+ return new AvailablePortProvider(List.of("cometd.port"));
+ }
+ }
+}
\ No newline at end of file
diff --git
a/tests/features/camel-cometd/src/test/resources/webapp/authenticated.html
b/tests/features/camel-cometd/src/test/resources/webapp/authenticated.html
new file mode 100644
index 00000000..654a4f03
--- /dev/null
+++ b/tests/features/camel-cometd/src/test/resources/webapp/authenticated.html
@@ -0,0 +1,65 @@
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+
+-->
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html>
+
+<head>
+<title>My Simple Ajax Example</title>
+
+<script type="text/javascript" src="dojo/dojo/dojo.js"></script>
+<script type="text/javascript">
+
+ dojo.require("dojox.cometd");
+
+ function setupComet() {
+ dojox.cometd.addListener('/meta/handshake', function(message) {
+ var auth = message.ext && message.ext.authentication;
+ if (auth && auth.failed === true) {
+ // Authentication failed, tell the user
+ window.alert('Authentication failed!');
+ }
+ });
+
+ dojox.cometd.init('/cometd', {
+ ext : {
+ authentication : {
+ user : 'changeit',
+ // change credentials to make init
work: correct credentials are 'changeit'
+ credentials : 'wrong'
+ }
+ }
+ });
+
+ dojox.cometd.subscribe("/channel/test", callback);
+ }
+
+ dojo.addOnLoad(setupComet);
+ dojo.addOnUnload(dojox.cometd, "disconnect");
+
+ function callback(msg) {
+ dojo.byId("MSG").innerHTML += msg.data + "\n";
+ }
+</script>
+
+</head>
+<body>
+<h1>CAMEL AUTHENTICATED TEST PAGE</h1>
+<pre id="MSG" />
+</body>
+</html>
diff --git
a/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojo/dojo.js
b/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojo/dojo.js
new file mode 100644
index 00000000..e6c146a3
--- /dev/null
+++ b/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojo/dojo.js
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+/*
+ Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved.
+ Available via Academic Free License >= 2.1 OR the modified BSD license.
+ see: http://dojotoolkit.org/license for details
+*/
+
+/*
+ This is an optimized version of Dojo, built for deployment and not for
+ development. To get sources and documentation, please visit:
+
+ http://dojotoolkit.org
+*/
+
+(function(){var _1=null;if((_1||(typeof
djConfig!="undefined"&&djConfig.scopeMap))&&(typeof window!="undefined")){var
_2="",_3="",_4="",_5={},_6={};_1=_1||djConfig.scopeMap;for(var
i=0;i<_1.length;i++){var _7=_1[i];_2+="var "+_7[0]+" = {}; "+_7[1]+" =
"+_7[0]+";"+_7[1]+"._scopeName =
'"+_7[1]+"';";_3+=(i==0?"":",")+_7[0];_4+=(i==0?"":",")+_7[1];_5[_7[0]]=_7[1];_6[_7[1]]=_7[0];}eval(_2+"dojo._scopeArgs
=
["+_4+"];");dojo._scopePrefixArgs=_3;dojo._scopePrefix="(function("+_3+"){";dojo._sco
[...]
diff --git
a/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojo/io/iframe.js
b/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojo/io/iframe.js
new file mode 100644
index 00000000..91f7d750
--- /dev/null
+++
b/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojo/io/iframe.js
@@ -0,0 +1,250 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+/*
+ Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved.
+ Available via Academic Free License >= 2.1 OR the modified BSD license.
+ see: http://dojotoolkit.org/license for details
+*/
+
+
+if(!dojo._hasResource["dojo.io.iframe"]){
+dojo._hasResource["dojo.io.iframe"]=true;
+dojo.provide("dojo.io.iframe");
+dojo.getObject("io",true,dojo);
+dojo.io.iframe={create:function(_1,_2,_3){
+if(window[_1]){
+return window[_1];
+}
+if(window.frames[_1]){
+return window.frames[_1];
+}
+var _4=null;
+var _5=_3;
+if(!_5){
+if(dojo.config["useXDomain"]&&!dojo.config["dojoBlankHtmlUrl"]){
+console.warn("dojo.io.iframe.create: When using cross-domain Dojo builds,"+"
please save dojo/resources/blank.html to your domain and set
djConfig.dojoBlankHtmlUrl"+" to the path on your domain to blank.html");
+}
+_5=(dojo.config["dojoBlankHtmlUrl"]||dojo.moduleUrl("dojo","resources/blank.html"));
+}
+var _4=dojo.place("<iframe id=\""+_1+"\" name=\""+_1+"\" src=\""+_5+"\"
onload=\""+_2+"\" style=\"position: absolute; left: 1px; top: 1px; height: 1px;
width: 1px; visibility: hidden\">",dojo.body());
+window[_1]=_4;
+return _4;
+},setSrc:function(_6,_7,_8){
+try{
+if(!_8){
+if(dojo.isWebKit){
+_6.location=_7;
+}else{
+frames[_6.name].location=_7;
+}
+}else{
+var _9;
+if(dojo.isIE||dojo.isWebKit){
+_9=_6.contentWindow.document;
+}else{
+_9=_6.contentWindow;
+}
+if(!_9){
+_6.location=_7;
+return;
+}else{
+_9.location.replace(_7);
+}
+}
+}
+catch(e){
+}
+},doc:function(_a){
+var
_b=_a.contentDocument||(((_a.name)&&(_a.document)&&(dojo.doc.getElementsByTagName("iframe")[_a.name].contentWindow)&&(dojo.doc.getElementsByTagName("iframe")[_a.name].contentWindow.document)))||((_a.name)&&(dojo.doc.frames[_a.name])&&(dojo.doc.frames[_a.name].document))||null;
+return _b;
+},send:function(_c){
+if(!this["_frame"]){
+this._frame=this.create(this._iframeName,dojo._scopeName+".io.iframe._iframeOnload();");
+}
+var _d=dojo._ioSetArgs(_c,function(_e){
+_e.canceled=true;
+_e.ioArgs._callNext();
+},function(_f){
+var _10=null;
+try{
+var _11=_f.ioArgs;
+var dii=dojo.io.iframe;
+var ifd=dii.doc(dii._frame);
+var _12=_11.handleAs;
+_10=ifd;
+if(_12!="html"){
+if(_12=="xml"){
+if(dojo.isIE<9||(dojo.isIE&&dojo.isQuirks)){
+dojo.query("a",dii._frame.contentWindow.document.documentElement).orphan();
+var _13=(dii._frame.contentWindow.document).documentElement.innerText;
+_13=_13.replace(/>\s+</g,"><");
+_13=dojo.trim(_13);
+var _14={responseText:_13};
+_10=dojo._contentHandlers["xml"](_14);
+}
+}else{
+_10=ifd.getElementsByTagName("textarea")[0].value;
+if(_12=="json"){
+_10=dojo.fromJson(_10);
+}else{
+if(_12=="javascript"){
+_10=dojo.eval(_10);
+}
+}
+}
+}
+}
+catch(e){
+_10=e;
+}
+finally{
+_11._callNext();
+}
+return _10;
+},function(_15,dfd){
+dfd.ioArgs._hasError=true;
+dfd.ioArgs._callNext();
+return _15;
+});
+_d.ioArgs._callNext=function(){
+if(!this["_calledNext"]){
+this._calledNext=true;
+dojo.io.iframe._currentDfd=null;
+dojo.io.iframe._fireNextRequest();
+}
+};
+this._dfdQueue.push(_d);
+this._fireNextRequest();
+dojo._ioWatch(_d,function(dfd){
+return !dfd.ioArgs["_hasError"];
+},function(dfd){
+return (!!dfd.ioArgs["_finished"]);
+},function(dfd){
+if(dfd.ioArgs._finished){
+dfd.callback(dfd);
+}else{
+dfd.errback(new Error("Invalid dojo.io.iframe request state"));
+}
+});
+return _d;
+},_currentDfd:null,_dfdQueue:[],_iframeName:dojo._scopeName+"IoIframe",_fireNextRequest:function(){
+try{
+if((this._currentDfd)||(this._dfdQueue.length==0)){
+return;
+}
+do{
+var dfd=this._currentDfd=this._dfdQueue.shift();
+}while(dfd&&dfd.canceled&&this._dfdQueue.length);
+if(!dfd||dfd.canceled){
+this._currentDfd=null;
+return;
+}
+var _16=dfd.ioArgs;
+var _17=_16.args;
+_16._contentToClean=[];
+var fn=dojo.byId(_17["form"]);
+var _18=_17["content"]||{};
+if(fn){
+if(_18){
+var _19=function(_1a,_1b){
+dojo.create("input",{type:"hidden",name:_1a,value:_1b},fn);
+_16._contentToClean.push(_1a);
+};
+for(var x in _18){
+var val=_18[x];
+if(dojo.isArray(val)&&val.length>1){
+var i;
+for(i=0;i<val.length;i++){
+_19(x,val[i]);
+}
+}else{
+if(!fn[x]){
+_19(x,val);
+}else{
+fn[x].value=val;
+}
+}
+}
+}
+var _1c=fn.getAttributeNode("action");
+var _1d=fn.getAttributeNode("method");
+var _1e=fn.getAttributeNode("target");
+if(_17["url"]){
+_16._originalAction=_1c?_1c.value:null;
+if(_1c){
+_1c.value=_17.url;
+}else{
+fn.setAttribute("action",_17.url);
+}
+}
+if(!_1d||!_1d.value){
+if(_1d){
+_1d.value=(_17["method"])?_17["method"]:"post";
+}else{
+fn.setAttribute("method",(_17["method"])?_17["method"]:"post");
+}
+}
+_16._originalTarget=_1e?_1e.value:null;
+if(_1e){
+_1e.value=this._iframeName;
+}else{
+fn.setAttribute("target",this._iframeName);
+}
+fn.target=this._iframeName;
+dojo._ioNotifyStart(dfd);
+fn.submit();
+}else{
+var _1f=_17.url+(_17.url.indexOf("?")>-1?"&":"?")+_16.query;
+dojo._ioNotifyStart(dfd);
+this.setSrc(this._frame,_1f,true);
+}
+}
+catch(e){
+dfd.errback(e);
+}
+},_iframeOnload:function(){
+var dfd=this._currentDfd;
+if(!dfd){
+this._fireNextRequest();
+return;
+}
+var _20=dfd.ioArgs;
+var _21=_20.args;
+var _22=dojo.byId(_21.form);
+if(_22){
+var _23=_20._contentToClean;
+for(var i=0;i<_23.length;i++){
+var key=_23[i];
+for(var j=0;j<_22.childNodes.length;j++){
+var _24=_22.childNodes[j];
+if(_24.name==key){
+dojo.destroy(_24);
+break;
+}
+}
+}
+if(_20["_originalAction"]){
+_22.setAttribute("action",_20._originalAction);
+}
+if(_20["_originalTarget"]){
+_22.setAttribute("target",_20._originalTarget);
+_22.target=_20._originalTarget;
+}
+}
+_20._finished=true;
+}};
+}
diff --git
a/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojo/io/script.js
b/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojo/io/script.js
new file mode 100644
index 00000000..ca42a2fe
--- /dev/null
+++
b/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojo/io/script.js
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+/*
+ Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved.
+ Available via Academic Free License >= 2.1 OR the modified BSD license.
+ see: http://dojotoolkit.org/license for details
+*/
+
+
+if(!dojo._hasResource["dojo.io.script"]){
+dojo._hasResource["dojo.io.script"]=true;
+dojo.provide("dojo.io.script");
+dojo.getObject("io",true,dojo);
+(function(){
+var _1=dojo.isIE?"onreadystatechange":"load",_2=/complete|loaded/;
+dojo.io.script={get:function(_3){
+var _4=this._makeScriptDeferred(_3);
+var _5=_4.ioArgs;
+dojo._ioAddQueryToUrl(_5);
+dojo._ioNotifyStart(_4);
+if(this._canAttach(_5)){
+var _6=this.attach(_5.id,_5.url,_3.frameDoc);
+if(!_5.jsonp&&!_5.args.checkString){
+var _7=dojo.connect(_6,_1,function(_8){
+if(_8.type=="load"||_2.test(_6.readyState)){
+dojo.disconnect(_7);
+_5.scriptLoaded=_8;
+}
+});
+}
+}
+dojo._ioWatch(_4,this._validCheck,this._ioCheck,this._resHandle);
+return _4;
+},attach:function(id,_9,_a){
+var _b=(_a||dojo.doc);
+var _c=_b.createElement("script");
+_c.type="text/javascript";
+_c.src=_9;
+_c.id=id;
+_c.charset="utf-8";
+return _b.getElementsByTagName("head")[0].appendChild(_c);
+},remove:function(id,_d){
+dojo.destroy(dojo.byId(id,_d));
+if(this["jsonp_"+id]){
+delete this["jsonp_"+id];
+}
+},_makeScriptDeferred:function(_e){
+var
_f=dojo._ioSetArgs(_e,this._deferredCancel,this._deferredOk,this._deferredError);
+var _10=_f.ioArgs;
+_10.id=dojo._scopeName+"IoScript"+(this._counter++);
+_10.canDelete=false;
+_10.jsonp=_e.callbackParamName||_e.jsonp;
+if(_10.jsonp){
+_10.query=_10.query||"";
+if(_10.query.length>0){
+_10.query+="&";
+}
+_10.query+=_10.jsonp+"="+(_e.frameDoc?"parent.":"")+dojo._scopeName+".io.script.jsonp_"+_10.id+"._jsonpCallback";
+_10.frameDoc=_e.frameDoc;
+_10.canDelete=true;
+_f._jsonpCallback=this._jsonpCallback;
+this["jsonp_"+_10.id]=_f;
+}
+return _f;
+},_deferredCancel:function(dfd){
+dfd.canceled=true;
+if(dfd.ioArgs.canDelete){
+dojo.io.script._addDeadScript(dfd.ioArgs);
+}
+},_deferredOk:function(dfd){
+var _11=dfd.ioArgs;
+if(_11.canDelete){
+dojo.io.script._addDeadScript(_11);
+}
+return _11.json||_11.scriptLoaded||_11;
+},_deferredError:function(_12,dfd){
+if(dfd.ioArgs.canDelete){
+if(_12.dojoType=="timeout"){
+dojo.io.script.remove(dfd.ioArgs.id,dfd.ioArgs.frameDoc);
+}else{
+dojo.io.script._addDeadScript(dfd.ioArgs);
+}
+}
+return _12;
+},_deadScripts:[],_counter:1,_addDeadScript:function(_13){
+dojo.io.script._deadScripts.push({id:_13.id,frameDoc:_13.frameDoc});
+_13.frameDoc=null;
+},_validCheck:function(dfd){
+var _14=dojo.io.script;
+var _15=_14._deadScripts;
+if(_15&&_15.length>0){
+for(var i=0;i<_15.length;i++){
+_14.remove(_15[i].id,_15[i].frameDoc);
+_15[i].frameDoc=null;
+}
+dojo.io.script._deadScripts=[];
+}
+return true;
+},_ioCheck:function(dfd){
+var _16=dfd.ioArgs;
+if(_16.json||(_16.scriptLoaded&&!_16.args.checkString)){
+return true;
+}
+var _17=_16.args.checkString;
+if(_17&&eval("typeof("+_17+") != 'undefined'")){
+return true;
+}
+return false;
+},_resHandle:function(dfd){
+if(dojo.io.script._ioCheck(dfd)){
+dfd.callback(dfd);
+}else{
+dfd.errback(new Error("inconceivable dojo.io.script._resHandle error"));
+}
+},_canAttach:function(_18){
+return true;
+},_jsonpCallback:function(_19){
+this.ioArgs.json=_19;
+}};
+})();
+}
diff --git
a/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojox/cometd.js
b/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojox/cometd.js
new file mode 100644
index 00000000..f053e2d1
--- /dev/null
+++ b/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojox/cometd.js
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+/**
+ * Dual licensed under the Apache License 2.0 and the MIT license.
+ * $Revision$ $Date: 2009-05-10 13:06:45 +1000 (Sun, 10 May 2009) $
+ */
+
+dojo.provide('dojox.cometd');
+dojo.registerModulePath('org','../org');
+dojo.require('org.cometd');
+dojo.require('dojo.io.script');
+
+// Remap cometd JSON functions to dojo JSON functions
+org.cometd.JSON.toJSON = dojo.toJson;
+org.cometd.JSON.fromJSON = dojo.fromJson;
+
+// The default cometd instance
+dojox.cometd = new org.cometd.Cometd();
+
+// Remap toolkit-specific transport calls
+dojox.cometd.LongPollingTransport = function()
+{
+ this.xhrSend = function(packet)
+ {
+ var deferred = dojo.rawXhrPost({
+ url: packet.url,
+ sync: packet.sync === true,
+ contentType: 'application/json;charset=UTF-8',
+ headers: packet.headers,
+ postData: packet.body,
+ handleAs: 'json',
+ load: packet.onSuccess,
+ error: function(error)
+ {
+ packet.onError(error.message, deferred ? deferred.ioArgs.error
: error);
+ }
+ });
+ return deferred.ioArgs.xhr;
+ };
+};
+dojox.cometd.LongPollingTransport.prototype = new
org.cometd.LongPollingTransport();
+dojox.cometd.LongPollingTransport.prototype.constructor =
dojox.cometd.LongPollingTransport;
+
+dojox.cometd.CallbackPollingTransport = function()
+{
+ this.jsonpSend = function(packet)
+ {
+ var deferred = dojo.io.script.get({
+ url: packet.url,
+ sync: packet.sync === true,
+ callbackParamName: 'jsonp',
+ content: {
+ // In callback-polling, the content must be sent via the
'message' parameter
+ message: packet.body
+ },
+ load: packet.onSuccess,
+ error: function(error)
+ {
+ packet.onError(error.message, deferred ? deferred.ioArgs.error
: error);
+ }
+ });
+ return undefined;
+ };
+};
+dojox.cometd.CallbackPollingTransport.prototype = new
org.cometd.CallbackPollingTransport();
+dojox.cometd.CallbackPollingTransport.prototype.constructor =
dojox.cometd.CallbackPollingTransport;
+
+dojox.cometd.registerTransport('long-polling', new
dojox.cometd.LongPollingTransport());
+dojox.cometd.registerTransport('callback-polling', new
dojox.cometd.CallbackPollingTransport());
+
+// Create a compatibility API for dojox.cometd instance with
+// the original API.
+
+dojox.cometd._init = dojox.cometd.init;
+
+dojox.cometd._unsubscribe = dojox.cometd.unsubscribe;
+
+dojox.cometd.unsubscribe = function(channelOrToken, objOrFunc, funcName)
+{
+ if (typeof channelOrToken === 'string')
+ {
+ throw "Deprecated function unsubscribe(string). Use
unsubscribe(object) passing as argument the return value of subscribe()";
+ }
+
+ dojox.cometd._unsubscribe(channelOrToken);
+};
+
+dojox.cometd._metaHandshakeEvent = function(event)
+{
+ event.action = "handshake";
+ dojo.publish("/cometd/meta", [event]);
+};
+
+dojox.cometd._metaConnectEvent = function(event)
+{
+ event.action = "connect";
+ dojo.publish("/cometd/meta", [event]);
+};
+
+dojox.cometd.addListener('/meta/handshake', dojox.cometd,
dojox.cometd._metaHandshakeEvent);
+dojox.cometd.addListener('/meta/connect', dojox.cometd,
dojox.cometd._metaConnectEvent);
diff --git
a/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojox/cometd.js.uncompressed.js
b/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojox/cometd.js.uncompressed.js
new file mode 100644
index 00000000..ce861e47
--- /dev/null
+++
b/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojox/cometd.js.uncompressed.js
@@ -0,0 +1,16 @@
+*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
diff --git
a/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojox/cometd.xd.js
b/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojox/cometd.xd.js
new file mode 100644
index 00000000..ce861e47
--- /dev/null
+++
b/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojox/cometd.xd.js
@@ -0,0 +1,16 @@
+*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
diff --git
a/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojox/cometd.xd.js.uncompressed.js
b/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojox/cometd.xd.js.uncompressed.js
new file mode 100644
index 00000000..ce861e47
--- /dev/null
+++
b/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojox/cometd.xd.js.uncompressed.js
@@ -0,0 +1,16 @@
+*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
diff --git
a/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojox/cometd/ack.js
b/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojox/cometd/ack.js
new file mode 100644
index 00000000..8812ef33
--- /dev/null
+++
b/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojox/cometd/ack.js
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+/**
+ * Dual licensed under the Apache License 2.0 and the MIT license.
+ * $Revision$ $Date: 2009-05-10 13:06:45 +1000 (Sun, 10 May 2009) $
+ */
+
+dojo.provide("dojox.cometd.ack");
+dojo.require("dojox.cometd");
+
+dojo.require("org.cometd.AckExtension");
+
+dojox.cometd.registerExtension('ack', new org.cometd.AckExtension());
\ No newline at end of file
diff --git
a/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojox/cometd/reload.js
b/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojox/cometd/reload.js
new file mode 100644
index 00000000..96eae035
--- /dev/null
+++
b/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojox/cometd/reload.js
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+/**
+ * Dual licensed under the Apache License 2.0 and the MIT license.
+ * $Revision$ $Date: 2009-05-10 13:06:45 +1000 (Sun, 10 May 2009) $
+ */
+
+dojo.provide("dojox.cometd.reload");
+dojo.require("dojox.cometd");
+
+dojo.require("dojo.cookie");
+dojo.require("org.cometd.ReloadExtension");
+
+// Remap cometd COOKIE functions to dojo cookie functions
+org.cometd.COOKIE.set = dojo.cookie;
+org.cometd.COOKIE.get = dojo.cookie;
+
+dojox.cometd.registerExtension('reload', new org.cometd.ReloadExtension());
+
+
diff --git
a/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojox/cometd/timestamp.js
b/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojox/cometd/timestamp.js
new file mode 100644
index 00000000..f7b08e22
--- /dev/null
+++
b/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojox/cometd/timestamp.js
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+/**
+ * Dual licensed under the Apache License 2.0 and the MIT license.
+ * $Revision$ $Date: 2009-05-10 13:06:45 +1000 (Sun, 10 May 2009) $
+ */
+
+dojo.provide("dojox.cometd.timestamp");
+dojo.require("dojox.cometd");
+dojo.require("org.cometd.TimeStampExtension");
+
+dojox.cometd.registerExtension('timestamp', new
org.cometd.TimeStampExtension());
\ No newline at end of file
diff --git
a/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojox/cometd/timesync.js
b/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojox/cometd/timesync.js
new file mode 100644
index 00000000..d10f9c8b
--- /dev/null
+++
b/tests/features/camel-cometd/src/test/resources/webapp/dojo/dojox/cometd/timesync.js
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+/**
+ * Dual licensed under the Apache License 2.0 and the MIT license.
+ * $Revision$ $Date: 2009-05-10 13:06:45 +1000 (Sun, 10 May 2009) $
+ */
+dojo.provide("dojox.cometd.timesync");
+dojo.require("dojox.cometd");
+dojo.require("org.cometd.TimeSyncExtension");
+
+dojox.cometd.timesync=new org.cometd.TimeSyncExtension();
+dojox.cometd.registerExtension('timesync', dojox.cometd.timesync);
\ No newline at end of file
diff --git
a/tests/features/camel-cometd/src/test/resources/webapp/dojo/org/cometd.js
b/tests/features/camel-cometd/src/test/resources/webapp/dojo/org/cometd.js
new file mode 100644
index 00000000..fa167fd6
--- /dev/null
+++ b/tests/features/camel-cometd/src/test/resources/webapp/dojo/org/cometd.js
@@ -0,0 +1,2427 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+/**
+ * Dual licensed under the Apache License 2.0 and the MIT license.
+ * $Revision$ $Date: 2011-03-22 10:32:04 +0100 (Tue, 22 Mar 2011) $
+ */
+
+// Dojo loader support
+if (typeof dojo !== 'undefined')
+{
+ dojo.provide('org.cometd');
+}
+else
+{
+ // Namespaces for the cometd implementation
+ this.org = this.org || {};
+ org.cometd = {};
+}
+
+// Abstract APIs
+org.cometd.JSON = {};
+org.cometd.JSON.toJSON = org.cometd.JSON.fromJSON = function(object)
+{
+ throw 'Abstract';
+};
+
+
+/**
+ * A registry for transports used by the Cometd object.
+ */
+org.cometd.TransportRegistry = function()
+{
+ var _types = [];
+ var _transports = {};
+
+ this.getTransportTypes = function()
+ {
+ return _types.slice(0);
+ };
+
+ this.findTransportTypes = function(version, crossDomain)
+ {
+ var result = [];
+ for (var i = 0; i < _types.length; ++i)
+ {
+ var type = _types[i];
+ if (_transports[type].accept(version, crossDomain) === true)
+ {
+ result.push(type);
+ }
+ }
+ return result;
+ };
+
+ this.negotiateTransport = function(types, version, crossDomain)
+ {
+ for (var i = 0; i < _types.length; ++i)
+ {
+ var type = _types[i];
+ for (var j = 0; j < types.length; ++j)
+ {
+ if (type == types[j])
+ {
+ var transport = _transports[type];
+ if (transport.accept(version, crossDomain) === true)
+ {
+ return transport;
+ }
+ }
+ }
+ }
+ return null;
+ };
+
+ this.add = function(type, transport, index)
+ {
+ var existing = false;
+ for (var i = 0; i < _types.length; ++i)
+ {
+ if (_types[i] == type)
+ {
+ existing = true;
+ break;
+ }
+ }
+
+ if (!existing)
+ {
+ if (typeof index !== 'number')
+ {
+ _types.push(type);
+ }
+ else
+ {
+ _types.splice(index, 0, type);
+ }
+ _transports[type] = transport;
+ }
+
+ return !existing;
+ };
+
+ this.remove = function(type)
+ {
+ for (var i = 0; i < _types.length; ++i)
+ {
+ if (_types[i] == type)
+ {
+ _types.splice(i, 1);
+ var transport = _transports[type];
+ delete _transports[type];
+ return transport;
+ }
+ }
+ return null;
+ };
+
+ this.reset = function()
+ {
+ for (var i = 0; i < _types.length; ++i)
+ {
+ _transports[_types[i]].reset();
+ }
+ };
+};
+
+
+/**
+ * The constructor for a Cometd object, identified by an optional name.
+ * The default name is the string 'default'.
+ * In the rare case a page needs more than one Bayeux conversation,
+ * a new instance can be created via:
+ * <pre>
+ * var bayeuxUrl2 = ...;
+ * var cometd2 = new $.Cometd();
+ * cometd2.init({url: bayeuxUrl2});
+ * </pre>
+ * @param name the optional name of this cometd object
+ */
+// IMPLEMENTATION NOTES:
+// Be very careful in not changing the function order and pass this file every
time through JSLint (http://jslint.com)
+// The only implied globals must be "dojo", "org" and "window", and check that
there are no "unused" warnings
+// Failing to pass JSLint may result in shrinkers/minifiers to create an
unusable file.
+org.cometd.Cometd = function(name)
+{
+ var _cometd = this;
+ var _name = name || 'default';
+ var _logLevel; // 'warn','info','debug'
+ var _url;
+ var _maxConnections;
+ var _backoffIncrement;
+ var _maxBackoff;
+ var _reverseIncomingExtensions;
+ var _maxNetworkDelay;
+ var _requestHeaders;
+ var _appendMessageTypeToURL;
+ var _autoBatch;
+ var _crossDomain = false;
+ var _transports = new org.cometd.TransportRegistry();
+ var _transport;
+ var _status = 'disconnected';
+ var _messageId = 0;
+ var _clientId = null;
+ var _batch = 0;
+ var _messageQueue = [];
+ var _internalBatch = false;
+ var _listeners = {};
+ var _backoff = 0;
+ var _scheduledSend = null;
+ var _extensions = [];
+ var _advice = {};
+ var _handshakeProps;
+ var _reestablish = false;
+ var _connected = true;
+
+ /**
+ * Mixes in the given objects into the target object by copying the
properties.
+ * @param deep if the copy must be deep
+ * @param target the target object
+ * @param objects the objects whose properties are copied into the target
+ */
+ function _mixin(deep, target, objects)
+ {
+ var result = target || {};
+
+ // Skip first 2 parameters (deep and target), and loop over the others
+ for (var i = 2; i < arguments.length; ++i)
+ {
+ var object = arguments[i];
+
+ if (object === undefined || object === null)
+ {
+ continue;
+ }
+
+ for (var propName in object)
+ {
+ var prop = object[propName];
+
+ // Avoid infinite loops
+ if (prop === target)
+ {
+ continue;
+ }
+ // Do not mixin undefined values
+ if (prop === undefined)
+ {
+ continue;
+ }
+
+ if (deep && typeof prop === 'object' && prop !== null)
+ {
+ if (prop instanceof Array)
+ {
+ result[propName] = _mixin(deep, [], prop);
+ }
+ else
+ {
+ result[propName] = _mixin(deep, {}, prop);
+ }
+ }
+ else
+ {
+ result[propName] = prop;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * This method is exposed as facility for extensions that may need to
clone messages.
+ */
+ this._mixin = _mixin;
+
+ /**
+ * Returns whether the given element is contained into the given array.
+ * @param element the element to check presence for
+ * @param array the array to check for the element presence
+ * @return the index of the element, if present, or a negative index if
the element is not present
+ */
+ function _inArray(element, array)
+ {
+ for (var i = 0; i < array.length; ++i)
+ {
+ if (element == array[i])
+ {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ function _isString(value)
+ {
+ if (value === undefined || value === null)
+ {
+ return false;
+ }
+ return typeof value === 'string' || value instanceof String;
+ }
+
+ function _isArray(value)
+ {
+ if (value === undefined || value === null)
+ {
+ return false;
+ }
+ return value instanceof Array;
+ }
+
+ function _isFunction(value)
+ {
+ if (value === undefined || value === null)
+ {
+ return false;
+ }
+ return typeof value === 'function';
+ }
+
+ function _log(level, args)
+ {
+ if (window.console)
+ {
+ var logger = window.console[level];
+ if (_isFunction(logger))
+ {
+ logger.apply(window.console, args);
+ }
+ }
+ }
+
+ function _warn()
+ {
+ _log('warn', arguments);
+ }
+ this._warn = _warn;
+
+ function _info()
+ {
+ if (_logLevel != 'warn')
+ {
+ _log('info', arguments);
+ }
+ }
+ this._info = _info;
+
+ function _debug()
+ {
+ if (_logLevel == 'debug')
+ {
+ _log('debug', arguments);
+ }
+ }
+ this._debug = _debug;
+
+ function _configure(configuration)
+ {
+ _debug('Configuring cometd object with', configuration);
+ // Support old style param, where only the Bayeux server URL was passed
+ if (_isString(configuration))
+ {
+ configuration = { url: configuration };
+ }
+ if (!configuration)
+ {
+ configuration = {};
+ }
+
+ _url = configuration.url;
+ if (!_url)
+ {
+ throw 'Missing required configuration parameter \'url\' specifying
the Bayeux server URL';
+ }
+ _maxConnections = configuration.maxConnections || 2;
+ _backoffIncrement = configuration.backoffIncrement || 1000;
+ _maxBackoff = configuration.maxBackoff || 60000;
+ _logLevel = configuration.logLevel || 'info';
+ _reverseIncomingExtensions = configuration.reverseIncomingExtensions
!== false;
+ _maxNetworkDelay = configuration.maxNetworkDelay || 10000;
+ _requestHeaders = configuration.requestHeaders || {};
+ _appendMessageTypeToURL = configuration.appendMessageTypeToURL !==
false;
+ _autoBatch = configuration.autoBatch === true;
+
+ // Check if we're cross domain
+ // [1] = protocol:, [2] = //host:port, [3] = host:port, [4] = host,
[5] = :port, [6] = port, [7] = uri, [8] = rest
+ var urlParts =
/(^https?:)?(\/\/(([^:\/\?#]+)(:(\d+))?))?([^\?#]*)(.*)?/.exec(_url);
+ _crossDomain = urlParts[3] && urlParts[3] != window.location.host;
+
+ // Check if appending extra path is supported
+ if (_appendMessageTypeToURL)
+ {
+ if (urlParts[8] !== undefined)
+ {
+ _info('Appending message type to URI ' + urlParts[7] +
urlParts[8] + ' is not supported, disabling \'appendMessageTypeToURL\'
configuration');
+ _appendMessageTypeToURL = false;
+ }
+ else
+ {
+ var uriSegments = urlParts[7].split('/');
+ var lastSegmentIndex = uriSegments.length - 1;
+ if (urlParts[7].match(/\/$/))
+ {
+ lastSegmentIndex -= 1;
+ }
+ if (uriSegments[lastSegmentIndex].indexOf('.') >= 0)
+ {
+ // Very likely the CometD servlet's URL pattern is mapped
to an extension, such as *.cometd
+ // It will be difficult to add the extra path in this case
+ _info('Appending message type to URI ' + urlParts[7] + '
is not supported, disabling \'appendMessageTypeToURL\' configuration');
+ _appendMessageTypeToURL = false;
+ }
+ }
+ }
+ }
+
+ function _clearSubscriptions()
+ {
+ for (var channel in _listeners)
+ {
+ var subscriptions = _listeners[channel];
+ for (var i = 0; i < subscriptions.length; ++i)
+ {
+ var subscription = subscriptions[i];
+ if (subscription && !subscription.listener)
+ {
+ delete subscriptions[i];
+ _debug('Removed subscription', subscription, 'for
channel', channel);
+ }
+ }
+ }
+ }
+
+ function _setStatus(newStatus)
+ {
+ if (_status != newStatus)
+ {
+ _debug('Status', _status, '->', newStatus);
+ _status = newStatus;
+ }
+ }
+
+ function _isDisconnected()
+ {
+ return _status == 'disconnecting' || _status == 'disconnected';
+ }
+
+ function _nextMessageId()
+ {
+ return ++_messageId;
+ }
+
+ function _applyExtension(scope, callback, name, message, outgoing)
+ {
+ try
+ {
+ return callback.call(scope, message);
+ }
+ catch (x)
+ {
+ _debug('Exception during execution of extension', name, x);
+ var exceptionCallback = _cometd.onExtensionException;
+ if (_isFunction(exceptionCallback))
+ {
+ _debug('Invoking extension exception callback', name, x);
+ try
+ {
+ exceptionCallback.call(_cometd, x, name, outgoing,
message);
+ }
+ catch(xx)
+ {
+ _info('Exception during execution of exception callback in
extension', name, xx);
+ }
+ }
+ return message;
+ }
+ }
+
+ function _applyIncomingExtensions(message)
+ {
+ for (var i = 0; i < _extensions.length; ++i)
+ {
+ if (message === undefined || message === null)
+ {
+ break;
+ }
+
+ var index = _reverseIncomingExtensions ? _extensions.length - 1 -
i : i;
+ var extension = _extensions[index];
+ var callback = extension.extension.incoming;
+ if (_isFunction(callback))
+ {
+ var result = _applyExtension(extension.extension, callback,
extension.name, message, false);
+ message = result === undefined ? message : result;
+ }
+ }
+ return message;
+ }
+
+ function _applyOutgoingExtensions(message)
+ {
+ for (var i = 0; i < _extensions.length; ++i)
+ {
+ if (message === undefined || message === null)
+ {
+ break;
+ }
+
+ var extension = _extensions[i];
+ var callback = extension.extension.outgoing;
+ if (_isFunction(callback))
+ {
+ var result = _applyExtension(extension.extension, callback,
extension.name, message, true);
+ message = result === undefined ? message : result;
+ }
+ }
+ return message;
+ }
+
+ /**
+ * Converts the given response into an array of bayeux messages
+ * @param response the response to convert
+ * @return an array of bayeux messages obtained by converting the response
+ */
+ function _convertToMessages(response)
+ {
+ if (_isString(response))
+ {
+ try
+ {
+ return org.cometd.JSON.fromJSON(response);
+ }
+ catch(x)
+ {
+ _debug('Could not convert to JSON the following string', '"' +
response + '"');
+ throw x;
+ }
+ }
+ if (_isArray(response))
+ {
+ return response;
+ }
+ if (response === undefined || response === null)
+ {
+ return [];
+ }
+ if (response instanceof Object)
+ {
+ return [response];
+ }
+ throw 'Conversion Error ' + response + ', typeof ' + (typeof response);
+ }
+
+ function _notify(channel, message)
+ {
+ var subscriptions = _listeners[channel];
+ if (subscriptions && subscriptions.length > 0)
+ {
+ for (var i = 0; i < subscriptions.length; ++i)
+ {
+ var subscription = subscriptions[i];
+ // Subscriptions may come and go, so the array may have 'holes'
+ if (subscription)
+ {
+ try
+ {
+ subscription.callback.call(subscription.scope,
message);
+ }
+ catch (x)
+ {
+ _debug('Exception during notification', subscription,
message, x);
+ var listenerCallback = _cometd.onListenerException;
+ if (_isFunction(listenerCallback))
+ {
+ _debug('Invoking listener exception callback',
subscription, x);
+ try
+ {
+ listenerCallback.call(_cometd, x,
subscription.handle, subscription.listener, message);
+ }
+ catch (xx)
+ {
+ _info('Exception during execution of listener
callback', subscription, xx);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ function _notifyListeners(channel, message)
+ {
+ // Notify direct listeners
+ _notify(channel, message);
+
+ // Notify the globbing listeners
+ var channelParts = channel.split('/');
+ var last = channelParts.length - 1;
+ for (var i = last; i > 0; --i)
+ {
+ var channelPart = channelParts.slice(0, i).join('/') + '/*';
+ // We don't want to notify /foo/* if the channel is /foo/bar/baz,
+ // so we stop at the first non recursive globbing
+ if (i == last)
+ {
+ _notify(channelPart, message);
+ }
+ // Add the recursive globber and notify
+ channelPart += '*';
+ _notify(channelPart, message);
+ }
+ }
+
+ function _setTimeout(funktion, delay)
+ {
+ return setTimeout(function()
+ {
+ try
+ {
+ funktion();
+ }
+ catch (x)
+ {
+ _debug('Exception invoking timed function', funktion, x);
+ }
+ }, delay);
+ }
+
+ function _cancelDelayedSend()
+ {
+ if (_scheduledSend !== null)
+ {
+ clearTimeout(_scheduledSend);
+ }
+ _scheduledSend = null;
+ }
+
+ function _delayedSend(operation)
+ {
+ _cancelDelayedSend();
+ var delay = _backoff;
+ if (_advice.interval && _advice.interval > 0)
+ {
+ delay += _advice.interval;
+ }
+ _scheduledSend = _setTimeout(operation, delay);
+ }
+
+ // Needed to break cyclic dependencies between function definitions
+ var _handleResponse;
+ var _handleFailure;
+
+ /**
+ * Delivers the messages to the CometD server
+ * @param messages the array of messages to send
+ * @param longpoll true if this send is a long poll
+ */
+ function _send(sync, messages, longpoll, extraPath)
+ {
+ // We must be sure that the messages have a clientId.
+ // This is not guaranteed since the handshake may take time to return
+ // (and hence the clientId is not known yet) and the application
+ // may create other messages.
+ for (var i = 0; i < messages.length; ++i)
+ {
+ var message = messages[i];
+ message.id = '' + _nextMessageId();
+ if (_clientId)
+ {
+ message.clientId = _clientId;
+ }
+ message = _applyOutgoingExtensions(message);
+ if (message !== undefined && message !== null)
+ {
+ messages[i] = message;
+ }
+ else
+ {
+ messages.splice(i--, 1);
+ }
+ }
+ if (messages.length === 0)
+ {
+ return;
+ }
+
+ var url = _url;
+ if (_appendMessageTypeToURL)
+ {
+ // If url does not end with '/', then append it
+ if (!url.match(/\/$/))
+ {
+ url = url + '/';
+ }
+ if (extraPath)
+ {
+ url = url + extraPath;
+ }
+ }
+
+ var envelope = {
+ url: url,
+ sync: sync,
+ messages: messages,
+ onSuccess: function(request, response)
+ {
+ try
+ {
+ _handleResponse.call(_cometd, request, response, longpoll);
+ }
+ catch (x)
+ {
+ _debug('Exception during handling of response', x);
+ }
+ },
+ onFailure: function(request, reason, exception)
+ {
+ try
+ {
+ _handleFailure.call(_cometd, request, messages, reason,
exception, longpoll);
+ }
+ catch (x)
+ {
+ _debug('Exception during handling of failure', x);
+ }
+ }
+ };
+ _debug('Send, sync=' + sync, envelope);
+ _transport.send(envelope, longpoll);
+ }
+
+ function _queueSend(message)
+ {
+ if (_batch > 0 || _internalBatch === true)
+ {
+ _messageQueue.push(message);
+ }
+ else
+ {
+ _send(false, [message], false);
+ }
+ }
+
+ /**
+ * Sends a complete bayeux message.
+ * This method is exposed as a public so that extensions may use it
+ * to send bayeux message directly, for example in case of re-sending
+ * messages that have already been sent but that for some reason must
+ * be resent.
+ */
+ this.send = _queueSend;
+
+ function _resetBackoff()
+ {
+ _backoff = 0;
+ }
+
+ function _increaseBackoff()
+ {
+ if (_backoff < _maxBackoff)
+ {
+ _backoff += _backoffIncrement;
+ }
+ }
+
+ /**
+ * Starts a the batch of messages to be sent in a single request.
+ * @see #_endBatch(sendMessages)
+ */
+ function _startBatch()
+ {
+ ++_batch;
+ }
+
+ function _flushBatch()
+ {
+ var messages = _messageQueue;
+ _messageQueue = [];
+ if (messages.length > 0)
+ {
+ _send(false, messages, false);
+ }
+ }
+
+ /**
+ * Ends the batch of messages to be sent in a single request,
+ * optionally sending messages present in the message queue depending
+ * on the given argument.
+ * @see #_startBatch()
+ */
+ function _endBatch()
+ {
+ --_batch;
+ if (_batch < 0)
+ {
+ throw 'Calls to startBatch() and endBatch() are not paired';
+ }
+
+ if (_batch === 0 && !_isDisconnected() && !_internalBatch)
+ {
+ _flushBatch();
+ }
+ }
+
+ /**
+ * Sends the connect message
+ */
+ function _connect()
+ {
+ if (!_isDisconnected())
+ {
+ var message = {
+ channel: '/meta/connect',
+ connectionType: _transport.getType()
+ };
+ if (!_connected)
+ {
+ _connected = true;
+ message.advice = { timeout: 0 };
+ }
+ _setStatus('connecting');
+ _debug('Connect sent', message);
+ _send(false, [message], true, 'connect');
+ _setStatus('connected');
+ }
+ }
+
+ function _delayedConnect()
+ {
+ _setStatus('connecting');
+ _delayedSend(function()
+ {
+ _connect();
+ });
+ }
+
+ /**
+ * Sends the initial handshake message
+ */
+ function _handshake(handshakeProps)
+ {
+ _clientId = null;
+
+ _clearSubscriptions();
+
+ // Reset the transports if we're not retrying the handshake
+ if (_isDisconnected())
+ {
+ _transports.reset();
+ }
+
+ _batch = 0;
+
+ // Mark the start of an internal batch.
+ // This is needed because handshake and connect are async.
+ // It may happen that the application calls init() then subscribe()
+ // and the subscribe message is sent before the connect message, if
+ // the subscribe message is not held until the connect message is sent.
+ // So here we start a batch to hold temporarily any message until
+ // the connection is fully established.
+ _internalBatch = true;
+
+ // Save the properties provided by the user, so that
+ // we can reuse them during automatic re-handshake
+ _handshakeProps = handshakeProps;
+
+ var version = '1.0';
+
+ // Figure out the transports to send to the server
+ var transportTypes = _transports.findTransportTypes(version,
_crossDomain);
+
+ var bayeuxMessage = {
+ version: version,
+ minimumVersion: '0.9',
+ channel: '/meta/handshake',
+ supportedConnectionTypes: transportTypes
+ };
+ // Do not allow the user to mess with the required properties,
+ // so merge first the user properties and *then* the bayeux message
+ var message = _mixin(false, {}, _handshakeProps, bayeuxMessage);
+
+ // Pick up the first available transport as initial transport
+ // since we don't know if the server supports it
+ _transport = _transports.negotiateTransport(transportTypes, version,
_crossDomain);
+ _debug('Initial transport is', _transport.getType());
+
+ // We started a batch to hold the application messages,
+ // so here we must bypass it and send immediately.
+ _setStatus('handshaking');
+ _debug('Handshake sent', message);
+ _send(false, [message], false, 'handshake');
+ }
+
+ function _delayedHandshake()
+ {
+ _setStatus('handshaking');
+
+ // We will call _handshake() which will reset _clientId, but we want
to avoid
+ // that between the end of this method and the call to _handshake()
someone may
+ // call publish() (or other methods that call _queueSend()).
+ _internalBatch = true;
+
+ _delayedSend(function()
+ {
+ _handshake(_handshakeProps);
+ });
+ }
+
+ function _handshakeResponse(message)
+ {
+ if (message.successful)
+ {
+ // Save clientId, figure out transport, then follow the advice to
connect
+ _clientId = message.clientId;
+
+ var newTransport =
_transports.negotiateTransport(message.supportedConnectionTypes,
message.version, _crossDomain);
+ if (newTransport === null)
+ {
+ throw 'Could not negotiate transport with server; client ' +
+ _transports.findTransportTypes(message.version,
_crossDomain) +
+ ', server ' + message.supportedConnectionTypes;
+ }
+ else if (_transport != newTransport)
+ {
+ _debug('Transport', _transport.getType(), '->',
newTransport.getType());
+ _transport = newTransport;
+ }
+
+ // End the internal batch and allow held messages from the
application
+ // to go to the server (see _handshake() where we start the
internal batch).
+ _internalBatch = false;
+ _flushBatch();
+
+ // Here the new transport is in place, as well as the clientId, so
+ // the listeners can perform a publish() if they want.
+ // Notify the listeners before the connect below.
+ message.reestablish = _reestablish;
+ _reestablish = true;
+ _notifyListeners('/meta/handshake', message);
+
+ if (!_isDisconnected())
+ {
+ if (_advice.reconnect != 'none')
+ {
+ _resetBackoff();
+ _delayedConnect();
+ }
+ else
+ {
+ _resetBackoff();
+ _setStatus('disconnected');
+ }
+ }
+ }
+ else
+ {
+ _notifyListeners('/meta/handshake', message);
+ _notifyListeners('/meta/unsuccessful', message);
+
+ // Only try again if we haven't been disconnected and
+ // the advice permits us to retry the handshake
+ if (!_isDisconnected())
+ {
+ if (_advice.reconnect != 'none')
+ {
+ _increaseBackoff();
+ _delayedHandshake();
+ }
+ else
+ {
+ _resetBackoff();
+ _setStatus('disconnected');
+ }
+ }
+ }
+ }
+
+ function _handshakeFailure(xhr, message)
+ {
+ // Notify listeners
+ var failureMessage = {
+ successful: false,
+ failure: true,
+ channel: '/meta/handshake',
+ request: message,
+ xhr: xhr,
+ advice: {
+ reconnect: 'retry',
+ interval: _backoff
+ }
+ };
+
+ _notifyListeners('/meta/handshake', failureMessage);
+ _notifyListeners('/meta/unsuccessful', failureMessage);
+
+ // Only try again if we haven't been disconnected and
+ // the advice permits us to retry the handshake
+ if (!_isDisconnected())
+ {
+ if (_advice.reconnect != 'none')
+ {
+ _increaseBackoff();
+ _delayedHandshake();
+ }
+ else
+ {
+ _resetBackoff();
+ _setStatus('disconnected');
+ }
+ }
+ }
+
+ function _connectResponse(message)
+ {
+ _connected = message.successful;
+ if (_connected)
+ {
+ // Notify the listeners after the status change but before the
next connect
+ _notifyListeners('/meta/connect', message);
+
+ // Connect was successful.
+ // Normally, the advice will say "reconnect: 'retry', interval: 0"
+ // and the server will hold the request, so when a response returns
+ // we immediately call the server again (long polling)
+ if (!_isDisconnected())
+ {
+ if (!_advice.reconnect || _advice.reconnect == 'retry')
+ {
+ _resetBackoff();
+ _delayedConnect();
+ }
+ else
+ {
+ _resetBackoff();
+ _setStatus('disconnected');
+ }
+ }
+ }
+ else
+ {
+ // Notify the listeners after the status change but before the
next action
+ _info('Connect failed:', message.error);
+ _notifyListeners('/meta/connect', message);
+ _notifyListeners('/meta/unsuccessful', message);
+
+ // Connect was not successful.
+ // This may happen when the server crashed, the current clientId
+ // will be invalid, and the server will ask to handshake again
+ if (!_isDisconnected())
+ {
+ var action = _advice.reconnect ? _advice.reconnect : 'retry';
+ switch (action)
+ {
+ case 'retry':
+ _increaseBackoff();
+ _delayedConnect();
+ break;
+ case 'handshake':
+ _resetBackoff();
+ _delayedHandshake();
+ break;
+ case 'none':
+ _resetBackoff();
+ _setStatus('disconnected');
+ break;
+ default:
+ _info('Unrecognized advice action', action);
+ break;
+ }
+ }
+ }
+ }
+
+ function _connectFailure(xhr, message)
+ {
+ _connected = false;
+
+ // Notify listeners
+ var failureMessage = {
+ successful: false,
+ failure: true,
+ channel: '/meta/connect',
+ request: message,
+ xhr: xhr,
+ advice: {
+ reconnect: 'retry',
+ interval: _backoff
+ }
+ };
+ _notifyListeners('/meta/connect', failureMessage);
+ _notifyListeners('/meta/unsuccessful', failureMessage);
+
+ if (!_isDisconnected())
+ {
+ var action = _advice.reconnect ? _advice.reconnect : 'retry';
+ switch (action)
+ {
+ case 'retry':
+ _increaseBackoff();
+ _delayedConnect();
+ break;
+ case 'handshake':
+ _resetBackoff();
+ _delayedHandshake();
+ break;
+ case 'none':
+ _resetBackoff();
+ _setStatus('disconnected');
+ break;
+ default:
+ _info('Unrecognized advice action', action);
+ break;
+ }
+ }
+ }
+
+ function _disconnect(abort)
+ {
+ _cancelDelayedSend();
+ if (abort)
+ {
+ _transport.abort();
+ }
+ _clientId = null;
+ _setStatus('disconnected');
+ _batch = 0;
+ _messageQueue = [];
+ _resetBackoff();
+ }
+
+ function _disconnectResponse(message)
+ {
+ if (message.successful)
+ {
+ _disconnect(false);
+ _notifyListeners('/meta/disconnect', message);
+ }
+ else
+ {
+ _disconnect(true);
+ _notifyListeners('/meta/disconnect', message);
+ _notifyListeners('/meta/unsuccessful', message);
+ }
+ }
+
+ function _disconnectFailure(xhr, message)
+ {
+ _disconnect(true);
+
+ var failureMessage = {
+ successful: false,
+ failure: true,
+ channel: '/meta/disconnect',
+ request: message,
+ xhr: xhr,
+ advice: {
+ reconnect: 'none',
+ interval: 0
+ }
+ };
+ _notifyListeners('/meta/disconnect', failureMessage);
+ _notifyListeners('/meta/unsuccessful', failureMessage);
+ }
+
+ function _subscribeResponse(message)
+ {
+ if (message.successful)
+ {
+ _notifyListeners('/meta/subscribe', message);
+ }
+ else
+ {
+ _info('Subscription to', message.subscription, 'failed:',
message.error);
+ _notifyListeners('/meta/subscribe', message);
+ _notifyListeners('/meta/unsuccessful', message);
+ }
+ }
+
+ function _subscribeFailure(xhr, message)
+ {
+ var failureMessage = {
+ successful: false,
+ failure: true,
+ channel: '/meta/subscribe',
+ request: message,
+ xhr: xhr,
+ advice: {
+ reconnect: 'none',
+ interval: 0
+ }
+ };
+ _notifyListeners('/meta/subscribe', failureMessage);
+ _notifyListeners('/meta/unsuccessful', failureMessage);
+ }
+
+ function _unsubscribeResponse(message)
+ {
+ if (message.successful)
+ {
+ _notifyListeners('/meta/unsubscribe', message);
+ }
+ else
+ {
+ _info('Unsubscription to', message.subscription, 'failed:',
message.error);
+ _notifyListeners('/meta/unsubscribe', message);
+ _notifyListeners('/meta/unsuccessful', message);
+ }
+ }
+
+ function _unsubscribeFailure(xhr, message)
+ {
+ var failureMessage = {
+ successful: false,
+ failure: true,
+ channel: '/meta/unsubscribe',
+ request: message,
+ xhr: xhr,
+ advice: {
+ reconnect: 'none',
+ interval: 0
+ }
+ };
+ _notifyListeners('/meta/unsubscribe', failureMessage);
+ _notifyListeners('/meta/unsuccessful', failureMessage);
+ }
+
+ function _messageResponse(message)
+ {
+ if (message.successful === undefined)
+ {
+ if (message.data)
+ {
+ // It is a plain message, and not a bayeux meta message
+ _notifyListeners(message.channel, message);
+ }
+ else
+ {
+ _debug('Unknown message', message);
+ }
+ }
+ else
+ {
+ if (message.successful)
+ {
+ _notifyListeners('/meta/publish', message);
+ }
+ else
+ {
+ _info('Publish failed:', message.error);
+ _notifyListeners('/meta/publish', message);
+ _notifyListeners('/meta/unsuccessful', message);
+ }
+ }
+ }
+
+ function _messageFailure(xhr, message)
+ {
+ var failureMessage = {
+ successful: false,
+ failure: true,
+ channel: message.channel,
+ request: message,
+ xhr: xhr,
+ advice: {
+ reconnect: 'none',
+ interval: 0
+ }
+ };
+ _notifyListeners('/meta/publish', failureMessage);
+ _notifyListeners('/meta/unsuccessful', failureMessage);
+ }
+
+ function _receive(message)
+ {
+ message = _applyIncomingExtensions(message);
+ if (message === undefined || message === null)
+ {
+ return;
+ }
+
+ if (message.advice)
+ {
+ _advice = message.advice;
+ }
+
+ var channel = message.channel;
+ switch (channel)
+ {
+ case '/meta/handshake':
+ _handshakeResponse(message);
+ break;
+ case '/meta/connect':
+ _connectResponse(message);
+ break;
+ case '/meta/disconnect':
+ _disconnectResponse(message);
+ break;
+ case '/meta/subscribe':
+ _subscribeResponse(message);
+ break;
+ case '/meta/unsubscribe':
+ _unsubscribeResponse(message);
+ break;
+ default:
+ _messageResponse(message);
+ break;
+ }
+ }
+
+ /**
+ * Receives a message.
+ * This method is exposed as a public so that extensions may inject
+ * messages simulating that they had been received.
+ */
+ this.receive = _receive;
+
+ _handleResponse = function _handleResponse(request, response, longpoll)
+ {
+ var messages = _convertToMessages(response);
+ _debug('Received', response, 'converted to', messages);
+
+ // Signal the transport it can send other queued requests
+ _transport.complete(request, true, longpoll);
+
+ for (var i = 0; i < messages.length; ++i)
+ {
+ var message = messages[i];
+ _receive(message);
+ }
+ };
+
+ _handleFailure = function _handleFailure(request, messages, reason,
exception, longpoll)
+ {
+ var xhr = request.xhr;
+
+ _debug('Failed', messages);
+
+ // Signal the transport it can send other queued requests
+ _transport.complete(request, false, longpoll);
+
+ for (var i = 0; i < messages.length; ++i)
+ {
+ var message = messages[i];
+ var channel = message.channel;
+ switch (channel)
+ {
+ case '/meta/handshake':
+ _handshakeFailure(xhr, message);
+ break;
+ case '/meta/connect':
+ _connectFailure(xhr, message);
+ break;
+ case '/meta/disconnect':
+ _disconnectFailure(xhr, message);
+ break;
+ case '/meta/subscribe':
+ _subscribeFailure(xhr, message);
+ break;
+ case '/meta/unsubscribe':
+ _unsubscribeFailure(xhr, message);
+ break;
+ default:
+ _messageFailure(xhr, message);
+ break;
+ }
+ }
+ };
+
+ function _hasSubscriptions(channel)
+ {
+ var subscriptions = _listeners[channel];
+ if (subscriptions)
+ {
+ for (var i = 0; i < subscriptions.length; ++i)
+ {
+ if (subscriptions[i])
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ function _resolveScopedCallback(scope, callback)
+ {
+ var delegate = {
+ scope: scope,
+ method: callback
+ };
+ if (_isFunction(scope))
+ {
+ delegate.scope = undefined;
+ delegate.method = scope;
+ }
+ else
+ {
+ if (_isString(callback))
+ {
+ if (!scope)
+ {
+ throw 'Invalid scope ' + scope;
+ }
+ delegate.method = scope[callback];
+ if (!_isFunction(delegate.method))
+ {
+ throw 'Invalid callback ' + callback + ' for scope ' +
scope;
+ }
+ }
+ else if (!_isFunction(callback))
+ {
+ throw 'Invalid callback ' + callback;
+ }
+ }
+ return delegate;
+ }
+
+ function _addListener(channel, scope, callback, isListener)
+ {
+ // The data structure is a map<channel, subscription[]>, where each
subscription
+ // holds the callback to be called and its scope.
+
+ var delegate = _resolveScopedCallback(scope, callback);
+ _debug('Adding listener on', channel, 'with scope', delegate.scope,
'and callback', delegate.method);
+
+ var subscription = {
+ channel: channel,
+ scope: delegate.scope,
+ callback: delegate.method,
+ listener: isListener
+ };
+
+ var subscriptions = _listeners[channel];
+ if (!subscriptions)
+ {
+ subscriptions = [];
+ _listeners[channel] = subscriptions;
+ }
+
+ // Pushing onto an array appends at the end and returns the id
associated with the element increased by 1.
+ // Note that if:
+ // a.push('a'); var hb=a.push('b'); delete a[hb-1]; var hc=a.push('c');
+ // then:
+ // hc==3, a.join()=='a',,'c', a.length==3
+ var subscriptionID = subscriptions.push(subscription) - 1;
+ subscription.id = subscriptionID;
+ subscription.handle = [channel, subscriptionID];
+
+ _debug('Added listener', subscription, 'for channel', channel, 'having
id =', subscriptionID);
+
+ // The subscription to allow removal of the listener is made of the
channel and the index
+ return subscription.handle;
+ }
+
+ function _removeListener(subscription)
+ {
+ var subscriptions = _listeners[subscription[0]];
+ if (subscriptions)
+ {
+ delete subscriptions[subscription[1]];
+ _debug('Removed listener', subscription);
+ }
+ }
+
+ //
+ // PUBLIC API
+ //
+
+ /**
+ * Registers the given transport under the given transport type.
+ * The optional index parameter specifies the "priority" at which the
+ * transport is registered (where 0 is the max priority).
+ * If a transport with the same type is already registered, this function
+ * does nothing and returns false.
+ * @param type the transport type
+ * @param transport the transport object
+ * @param index the index at which this transport is to be registered
+ * @return true if the transport has been registered, false otherwise
+ * @see #unregisterTransport(type)
+ */
+ this.registerTransport = function(type, transport, index)
+ {
+ var result = _transports.add(type, transport, index);
+ if (result)
+ {
+ _debug('Registered transport', type);
+
+ if (_isFunction(transport.registered))
+ {
+ transport.registered(type, this);
+ }
+ }
+ return result;
+ };
+
+ /**
+ * @return an array of all registered transport types
+ */
+ this.getTransportTypes = function()
+ {
+ return _transports.getTransportTypes();
+ };
+
+ /**
+ * Unregisters the transport with the given transport type.
+ * @param type the transport type to unregister
+ * @return the transport that has been unregistered,
+ * or null if no transport was previously registered under the given
transport type
+ */
+ this.unregisterTransport = function(type)
+ {
+ var transport = _transports.remove(type);
+ if (transport !== null)
+ {
+ _debug('Unregistered transport', type);
+
+ if (_isFunction(transport.unregistered))
+ {
+ transport.unregistered();
+ }
+ }
+ return transport;
+ };
+
+ /**
+ * Configures the initial Bayeux communication with the Bayeux server.
+ * Configuration is passed via an object that must contain a mandatory
field <code>url</code>
+ * of type string containing the URL of the Bayeux server.
+ * @param configuration the configuration object
+ */
+ this.configure = function(configuration)
+ {
+ _configure.call(this, configuration);
+ };
+
+ /**
+ * Configures and establishes the Bayeux communication with the Bayeux
server
+ * via a handshake and a subsequent connect.
+ * @param configuration the configuration object
+ * @param handshakeProps an object to be merged with the handshake message
+ * @see #configure(configuration)
+ * @see #handshake(handshakeProps)
+ */
+ this.init = function(configuration, handshakeProps)
+ {
+ this.configure(configuration);
+ this.handshake(handshakeProps);
+ };
+
+ /**
+ * Establishes the Bayeux communication with the Bayeux server
+ * via a handshake and a subsequent connect.
+ * @param handshakeProps an object to be merged with the handshake message
+ */
+ this.handshake = function(handshakeProps)
+ {
+ _setStatus('disconnected');
+ _reestablish = false;
+ _handshake(handshakeProps);
+ };
+
+ /**
+ * Disconnects from the Bayeux server.
+ * It is possible to suggest to attempt a synchronous disconnect, but this
feature
+ * may only be available in certain transports (for example, long-polling
may support
+ * it, callback-polling certainly does not).
+ * @param sync whether attempt to perform a synchronous disconnect
+ * @param disconnectProps an object to be merged with the disconnect
message
+ */
+ this.disconnect = function(sync, disconnectProps)
+ {
+ if (_isDisconnected())
+ {
+ return;
+ }
+
+ if (disconnectProps === undefined)
+ {
+ if (typeof sync !== 'boolean')
+ {
+ disconnectProps = sync;
+ sync = false;
+ }
+ }
+
+ var bayeuxMessage = {
+ channel: '/meta/disconnect'
+ };
+ var message = _mixin(false, {}, disconnectProps, bayeuxMessage);
+ _setStatus('disconnecting');
+ _send(sync === true, [message], false, 'disconnect');
+ };
+
+ /**
+ * Marks the start of a batch of application messages to be sent to the
server
+ * in a single request, obtaining a single response containing (possibly)
many
+ * application reply messages.
+ * Messages are held in a queue and not sent until {@link #endBatch()} is
called.
+ * If startBatch() is called multiple times, then an equal number of
endBatch()
+ * calls must be made to close and send the batch of messages.
+ * @see #endBatch()
+ */
+ this.startBatch = function()
+ {
+ _startBatch();
+ };
+
+ /**
+ * Marks the end of a batch of application messages to be sent to the
server
+ * in a single request.
+ * @see #startBatch()
+ */
+ this.endBatch = function()
+ {
+ _endBatch();
+ };
+
+ /**
+ * Executes the given callback in the given scope, surrounded by a {@link
#startBatch()}
+ * and {@link #endBatch()} calls.
+ * @param scope the scope of the callback, may be omitted
+ * @param callback the callback to be executed within {@link
#startBatch()} and {@link #endBatch()} calls
+ */
+ this.batch = function(scope, callback)
+ {
+ var delegate = _resolveScopedCallback(scope, callback);
+ this.startBatch();
+ try
+ {
+ delegate.method.call(delegate.scope);
+ this.endBatch();
+ }
+ catch (x)
+ {
+ _debug('Exception during execution of batch', x);
+ this.endBatch();
+ throw x;
+ }
+ };
+
+ /**
+ * Adds a listener for bayeux messages, performing the given callback in
the given scope
+ * when a message for the given channel arrives.
+ * @param channel the channel the listener is interested to
+ * @param scope the scope of the callback, may be omitted
+ * @param callback the callback to call when a message is sent to the
channel
+ * @returns the subscription handle to be passed to {@link
#removeListener(object)}
+ * @see #removeListener(subscription)
+ */
+ this.addListener = function(channel, scope, callback)
+ {
+ if (arguments.length < 2)
+ {
+ throw 'Illegal arguments number: required 2, got ' +
arguments.length;
+ }
+ if (!_isString(channel))
+ {
+ throw 'Illegal argument type: channel must be a string';
+ }
+
+ return _addListener(channel, scope, callback, true);
+ };
+
+ /**
+ * Removes the subscription obtained with a call to {@link
#addListener(string, object, function)}.
+ * @param subscription the subscription to unsubscribe.
+ * @see #addListener(channel, scope, callback)
+ */
+ this.removeListener = function(subscription)
+ {
+ if (!_isArray(subscription))
+ {
+ throw 'Invalid argument: expected subscription, not ' +
subscription;
+ }
+
+ _removeListener(subscription);
+ };
+
+ /**
+ * Removes all listeners registered with {@link #addListener(channel,
scope, callback)} or
+ * {@link #subscribe(channel, scope, callback)}.
+ */
+ this.clearListeners = function()
+ {
+ _listeners = {};
+ };
+
+ /**
+ * Subscribes to the given channel, performing the given callback in the
given scope
+ * when a message for the channel arrives.
+ * @param channel the channel to subscribe to
+ * @param scope the scope of the callback, may be omitted
+ * @param callback the callback to call when a message is sent to the
channel
+ * @param subscribeProps an object to be merged with the subscribe message
+ * @return the subscription handle to be passed to {@link
#unsubscribe(object)}
+ */
+ this.subscribe = function(channel, scope, callback, subscribeProps)
+ {
+ if (arguments.length < 2)
+ {
+ throw 'Illegal arguments number: required 2, got ' +
arguments.length;
+ }
+ if (!_isString(channel))
+ {
+ throw 'Illegal argument type: channel must be a string';
+ }
+ if (_isDisconnected())
+ {
+ throw 'Illegal state: already disconnected';
+ }
+
+ // Normalize arguments
+ if (_isFunction(scope))
+ {
+ subscribeProps = callback;
+ callback = scope;
+ scope = undefined;
+ }
+
+ // Only send the message to the server if this clientId has not yet
subscribed to the channel
+ var send = !_hasSubscriptions(channel);
+
+ var subscription = _addListener(channel, scope, callback, false);
+
+ if (send)
+ {
+ // Send the subscription message after the subscription
registration to avoid
+ // races where the server would send a message to the subscribers,
but here
+ // on the client the subscription has not been added yet to the
data structures
+ var bayeuxMessage = {
+ channel: '/meta/subscribe',
+ subscription: channel
+ };
+ var message = _mixin(false, {}, subscribeProps, bayeuxMessage);
+ _queueSend(message);
+ }
+
+ return subscription;
+ };
+
+ /**
+ * Unsubscribes the subscription obtained with a call to {@link
#subscribe(string, object, function)}.
+ * @param subscription the subscription to unsubscribe.
+ */
+ this.unsubscribe = function(subscription, unsubscribeProps)
+ {
+ if (arguments.length < 1)
+ {
+ throw 'Illegal arguments number: required 1, got ' +
arguments.length;
+ }
+ if (_isDisconnected())
+ {
+ throw 'Illegal state: already disconnected';
+ }
+
+ // Remove the local listener before sending the message
+ // This ensures that if the server fails, this client does not get
notifications
+ this.removeListener(subscription);
+
+ var channel = subscription[0];
+ // Only send the message to the server if this clientId unsubscribes
the last subscription
+ if (!_hasSubscriptions(channel))
+ {
+ var bayeuxMessage = {
+ channel: '/meta/unsubscribe',
+ subscription: channel
+ };
+ var message = _mixin(false, {}, unsubscribeProps, bayeuxMessage);
+ _queueSend(message);
+ }
+ };
+
+ /**
+ * Removes all subscriptions added via {@link #subscribe(channel, scope,
callback, subscribeProps)},
+ * but does not remove the listeners added via {@link addListener(channel,
scope, callback)}.
+ */
+ this.clearSubscriptions = function()
+ {
+ _clearSubscriptions();
+ };
+
+ /**
+ * Publishes a message on the given channel, containing the given content.
+ * @param channel the channel to publish the message to
+ * @param content the content of the message
+ * @param publishProps an object to be merged with the publish message
+ */
+ this.publish = function(channel, content, publishProps)
+ {
+ if (arguments.length < 1)
+ {
+ throw 'Illegal arguments number: required 1, got ' +
arguments.length;
+ }
+ if (!_isString(channel))
+ {
+ throw 'Illegal argument type: channel must be a string';
+ }
+ if (_isDisconnected())
+ {
+ throw 'Illegal state: already disconnected';
+ }
+
+ var bayeuxMessage = {
+ channel: channel,
+ data: content
+ };
+ var message = _mixin(false, {}, publishProps, bayeuxMessage);
+ _queueSend(message);
+ };
+
+ /**
+ * Returns a string representing the status of the bayeux communication
with the Bayeux server.
+ */
+ this.getStatus = function()
+ {
+ return _status;
+ };
+
+ /**
+ * Returns whether this instance has been disconnected.
+ */
+ this.isDisconnected = _isDisconnected;
+
+ /**
+ * Sets the backoff period used to increase the backoff time when retrying
an unsuccessful or failed message.
+ * Default value is 1 second, which means if there is a persistent failure
the retries will happen
+ * after 1 second, then after 2 seconds, then after 3 seconds, etc. So for
example with 15 seconds of
+ * elapsed time, there will be 5 retries (at 1, 3, 6, 10 and 15 seconds
elapsed).
+ * @param period the backoff period to set
+ * @see #getBackoffIncrement()
+ */
+ this.setBackoffIncrement = function(period)
+ {
+ _backoffIncrement = period;
+ };
+
+ /**
+ * Returns the backoff period used to increase the backoff time when
retrying an unsuccessful or failed message.
+ * @see #setBackoffIncrement(period)
+ */
+ this.getBackoffIncrement = function()
+ {
+ return _backoffIncrement;
+ };
+
+ /**
+ * Returns the backoff period to wait before retrying an unsuccessful or
failed message.
+ */
+ this.getBackoffPeriod = function()
+ {
+ return _backoff;
+ };
+
+ /**
+ * Sets the log level for console logging.
+ * Valid values are the strings 'error', 'warn', 'info' and 'debug', from
+ * less verbose to more verbose.
+ * @param level the log level string
+ */
+ this.setLogLevel = function(level)
+ {
+ _logLevel = level;
+ };
+
+ /**
+ * Registers an extension whose callbacks are called for every incoming
message
+ * (that comes from the server to this client implementation) and for every
+ * outgoing message (that originates from this client implementation for
the
+ * server).
+ * The format of the extension object is the following:
+ * <pre>
+ * {
+ * incoming: function(message) { ... },
+ * outgoing: function(message) { ... }
+ * }
+ * </pre>
+ * Both properties are optional, but if they are present they will be
called
+ * respectively for each incoming message and for each outgoing message.
+ * @param name the name of the extension
+ * @param extension the extension to register
+ * @return true if the extension was registered, false otherwise
+ * @see #unregisterExtension(name)
+ */
+ this.registerExtension = function(name, extension)
+ {
+ if (arguments.length < 2)
+ {
+ throw 'Illegal arguments number: required 2, got ' +
arguments.length;
+ }
+ if (!_isString(name))
+ {
+ throw 'Illegal argument type: extension name must be a string';
+ }
+
+ var existing = false;
+ for (var i = 0; i < _extensions.length; ++i)
+ {
+ var existingExtension = _extensions[i];
+ if (existingExtension.name == name)
+ {
+ existing = true;
+ break;
+ }
+ }
+ if (!existing)
+ {
+ _extensions.push({
+ name: name,
+ extension: extension
+ });
+ _debug('Registered extension', name);
+
+ // Callback for extensions
+ if (_isFunction(extension.registered))
+ {
+ extension.registered(name, this);
+ }
+
+ return true;
+ }
+ else
+ {
+ _info('Could not register extension with name', name, 'since
another extension with the same name already exists');
+ return false;
+ }
+ };
+
+ /**
+ * Unregister an extension previously registered with
+ * {@link #registerExtension(name, extension)}.
+ * @param name the name of the extension to unregister.
+ * @return true if the extension was unregistered, false otherwise
+ */
+ this.unregisterExtension = function(name)
+ {
+ if (!_isString(name))
+ {
+ throw 'Illegal argument type: extension name must be a string';
+ }
+
+ var unregistered = false;
+ for (var i = 0; i < _extensions.length; ++i)
+ {
+ var extension = _extensions[i];
+ if (extension.name == name)
+ {
+ _extensions.splice(i, 1);
+ unregistered = true;
+ _debug('Unregistered extension', name);
+
+ // Callback for extensions
+ var ext = extension.extension;
+ if (_isFunction(ext.unregistered))
+ {
+ ext.unregistered();
+ }
+
+ break;
+ }
+ }
+ return unregistered;
+ };
+
+ /**
+ * Find the extension registered with the given name.
+ * @param name the name of the extension to find
+ * @return the extension found or null if no extension with the given name
has been registered
+ */
+ this.getExtension = function(name)
+ {
+ for (var i = 0; i < _extensions.length; ++i)
+ {
+ var extension = _extensions[i];
+ if (extension.name == name)
+ {
+ return extension.extension;
+ }
+ }
+ return null;
+ };
+
+ /**
+ * Returns the name assigned to this Cometd object, or the string 'default'
+ * if no name has been explicitly passed as parameter to the constructor.
+ */
+ this.getName = function()
+ {
+ return _name;
+ };
+
+ /**
+ * Returns the clientId assigned by the Bayeux server during handshake.
+ */
+ this.getClientId = function()
+ {
+ return _clientId;
+ };
+
+ /**
+ * Returns the URL of the Bayeux server.
+ */
+ this.getURL = function()
+ {
+ return _url;
+ };
+
+ this.getTransport = function()
+ {
+ return _transport;
+ };
+
+ /**
+ * Base object with the common functionality for transports.
+ * The key responsibility is to allow at most 2 outstanding requests to
the server,
+ * to avoid that requests are sent behind a long poll.
+ * To achieve this, we have one reserved request for the long poll, and
all other
+ * requests are serialized one after the other.
+ */
+ org.cometd.Transport = function()
+ {
+ var self = this;
+ var _type;
+ var _requestIds = 0;
+ var _longpollRequest = null;
+ var _requests = [];
+ var _envelopes = [];
+
+ /**
+ * Function invoked just after a transport has been successfully
registered.
+ * @param type the type of transport (for example 'long-polling')
+ * @param cometd the cometd object this transport has been registered
to
+ * @see #unregistered()
+ */
+ this.registered = function(type, cometd)
+ {
+ _type = type;
+ };
+
+ /**
+ * Function invoked just after a transport has been successfully
unregistered.
+ * @see #registered(type, cometd)
+ */
+ this.unregistered = function()
+ {
+ _type = null;
+ };
+
+ /**
+ * Returns whether this transport can work for the given version and
cross domain communication case.
+ * @param version a string indicating the transport version
+ * @param crossDomain a boolean indicating whether the communication
is cross domain
+ * @return true if this transport can work for the given version and
cross domain communication case,
+ * false otherwise
+ */
+ this.accept = function(version, crossDomain)
+ {
+ throw 'Abstract';
+ };
+
+ /**
+ * Performs the actual send depending on the transport type details.
+ * @param envelope the envelope to send
+ * @param request the request information
+ */
+ this.transportSend = function(envelope, request)
+ {
+ throw 'Abstract';
+ };
+
+ this.transportSuccess = function(envelope, request, response)
+ {
+ if (!request.expired)
+ {
+ clearTimeout(request.timeout);
+ if (response && response.length > 0)
+ {
+ envelope.onSuccess(request, response);
+ }
+ else
+ {
+ envelope.onFailure(request, 'Empty HTTP response');
+ }
+ }
+ };
+
+ this.transportFailure = function(envelope, request, reason, exception)
+ {
+ if (!request.expired)
+ {
+ clearTimeout(request.timeout);
+ envelope.onFailure(request, reason, exception);
+ }
+ };
+
+ function _transportSend(envelope, request)
+ {
+ request.expired = false;
+
+ this.transportSend(envelope, request);
+
+ if (!envelope.sync)
+ {
+ var delay = _maxNetworkDelay;
+ if (request.longpoll === true)
+ {
+ delay +=_advice && typeof _advice.timeout === 'number' ?
_advice.timeout : 0;
+ }
+ request.timeout = _setTimeout(function()
+ {
+ request.expired = true;
+ if (request.xhr)
+ {
+ request.xhr.abort();
+ }
+ var errorMessage = 'Transport ' + self.getType() + '
exceeded ' + delay +
+ ' ms max network delay for request ' + request.id;
+ _debug(errorMessage);
+ envelope.onFailure(request, 'timeout', errorMessage);
+ }, delay);
+ }
+ }
+
+ function _longpollSend(envelope)
+ {
+ if (_longpollRequest !== null)
+ {
+ throw 'Concurrent longpoll requests not allowed, request ' +
_longpollRequest.id + ' not yet completed';
+ }
+
+ var requestId = ++_requestIds;
+ var request = {
+ id: requestId,
+ longpoll: true
+ };
+ _transportSend.call(this, envelope, request);
+ _longpollRequest = request;
+ }
+
+ function _queueSend(envelope)
+ {
+ var requestId = ++_requestIds;
+ var request = {
+ id: requestId,
+ longpoll: false
+ };
+ // Consider the longpoll requests which should always be present
+ if (_requests.length < _maxConnections - 1)
+ {
+ _debug('Transport sending request', requestId, envelope);
+ _transportSend.call(this, envelope, request);
+ _requests.push(request);
+ }
+ else
+ {
+ _debug('Transport queueing request', requestId, envelope);
+ _envelopes.push([envelope, request]);
+ }
+ }
+
+ function _longpollComplete(request)
+ {
+ var requestId = request.id;
+ if (_longpollRequest !== null && _longpollRequest !== request)
+ {
+ throw 'Longpoll request mismatch, completing request ' +
requestId;
+ }
+
+ // Reset longpoll request
+ _longpollRequest = null;
+ }
+
+ function _coalesceEnvelopes(envelope)
+ {
+ while (_envelopes.length > 0)
+ {
+ var envelopeAndRequest = _envelopes[0];
+ var newEnvelope = envelopeAndRequest[0];
+ var newRequest = envelopeAndRequest[1];
+ if (newEnvelope.url === envelope.url &&
+ newEnvelope.sync === envelope.sync)
+ {
+ _envelopes.shift();
+ envelope.messages =
envelope.messages.concat(newEnvelope.messages);
+ _debug('Coalesced', newEnvelope.messages.length, 'messages
from request', newRequest.id);
+ continue;
+ }
+ break;
+ }
+ }
+
+ function _complete(request, success)
+ {
+ var index = _inArray(request, _requests);
+ // The index can be negative if the request has been aborted
+ if (index >= 0)
+ {
+ _requests.splice(index, 1);
+ }
+
+ if (_envelopes.length > 0)
+ {
+ var envelopeAndRequest = _envelopes.shift();
+ var nextEnvelope = envelopeAndRequest[0];
+ var nextRequest = envelopeAndRequest[1];
+ _debug('Transport dequeued request', nextRequest.id);
+ if (success)
+ {
+ if (_autoBatch)
+ {
+ _coalesceEnvelopes(nextEnvelope);
+ }
+ _queueSend.call(this, nextEnvelope);
+ _debug('Transport completed request', request.id,
nextEnvelope);
+ }
+ else
+ {
+ // Keep the semantic of calling response callbacks
asynchronously after the request
+ setTimeout(function() {
nextEnvelope.onFailure(nextRequest, 'error'); }, 0);
+ }
+ }
+ }
+
+ /**
+ * Returns the type of this transport.
+ * @see #registered(type, cometd)
+ */
+ this.getType = function()
+ {
+ return _type;
+ };
+
+ this.send = function(envelope, longpoll)
+ {
+ if (longpoll)
+ {
+ _longpollSend.call(this, envelope);
+ }
+ else
+ {
+ _queueSend.call(this, envelope);
+ }
+ };
+
+ this.complete = function(request, success, longpoll)
+ {
+ if (longpoll)
+ {
+ _longpollComplete.call(this, request);
+ }
+ else
+ {
+ _complete.call(this, request, success);
+ }
+ };
+
+ this.abort = function()
+ {
+ for (var i = 0; i < _requests.length; ++i)
+ {
+ var request = _requests[i];
+ _debug('Aborting request', request);
+ if (request.xhr)
+ {
+ request.xhr.abort();
+ }
+ }
+ if (_longpollRequest)
+ {
+ _debug('Aborting longpoll request', _longpollRequest);
+ if (_longpollRequest.xhr)
+ {
+ _longpollRequest.xhr.abort();
+ }
+ }
+ this.reset();
+ };
+
+ this.reset = function()
+ {
+ _longpollRequest = null;
+ _requests = [];
+ _envelopes = [];
+ };
+
+ this.toString = function()
+ {
+ return this.getType();
+ };
+ };
+
+ org.cometd.LongPollingTransport = function()
+ {
+ var self = this;
+ // By default, support cross domain
+ var _supportsCrossDomain = true;
+
+ this.accept = function(version, crossDomain)
+ {
+ return _supportsCrossDomain || !crossDomain;
+ };
+
+ this.xhrSend = function(packet)
+ {
+ throw 'Abstract';
+ };
+
+ this.transportSend = function(envelope, request)
+ {
+ try
+ {
+ var sameStack = true;
+ request.xhr = this.xhrSend({
+ transport: this,
+ url: envelope.url,
+ sync: envelope.sync,
+ headers: _requestHeaders,
+ body: org.cometd.JSON.toJSON(envelope.messages),
+ onSuccess: function(response)
+ {
+ if (!response || response.length === 0)
+ {
+ _supportsCrossDomain = false;
+ }
+ self.transportSuccess(envelope, request, response);
+ },
+ onError: function(reason, exception)
+ {
+ _supportsCrossDomain = false;
+ if (sameStack)
+ {
+ // Keep the semantic of calling response callbacks
asynchronously after the request
+ _setTimeout(function()
+ {
+ self.transportFailure(envelope, request,
reason, exception);
+ }, 0);
+ }
+ else
+ {
+ self.transportFailure(envelope, request, reason,
exception);
+ }
+ }
+ });
+ sameStack = false;
+ }
+ catch (x)
+ {
+ _supportsCrossDomain = false;
+ // Keep the semantic of calling response callbacks
asynchronously after the request
+ _setTimeout(function()
+ {
+ self.transportFailure(envelope, request, 'error', x);
+ }, 0);
+ }
+ };
+
+ this.reset = function()
+ {
+ org.cometd.LongPollingTransport.prototype.reset();
+ _supportsCrossDomain = true;
+ };
+ };
+ org.cometd.LongPollingTransport.prototype = new org.cometd.Transport();
+ org.cometd.LongPollingTransport.prototype.constructor =
org.cometd.LongPollingTransport;
+
+ org.cometd.CallbackPollingTransport = function()
+ {
+ var self = this;
+ var _maxLength = 2000;
+
+ this.accept = function(version, crossDomain)
+ {
+ return true;
+ };
+
+ this.jsonpSend = function(packet)
+ {
+ throw 'Abstract';
+ };
+
+ this.transportSend = function(envelope, request)
+ {
+ // Microsoft Internet Explorer has a 2083 URL max length
+ // We must ensure that we stay within that length
+ var messages = org.cometd.JSON.toJSON(envelope.messages);
+ // Encode the messages because all brackets, quotes, commas,
colons, etc
+ // present in the JSON will be URL encoded, taking many more
characters
+ var urlLength = envelope.url.length + encodeURI(messages).length;
+
+ // Let's stay on the safe side and use 2000 instead of 2083
+ // also because we did not count few characters among which
+ // the parameter name 'message' and the parameter 'jsonp',
+ // which sum up to about 50 chars
+ if (urlLength > _maxLength)
+ {
+ var x = envelope.messages.length > 1 ?
+ 'Too many bayeux messages in the same batch resulting
in message too big ' +
+ '(' + urlLength + ' bytes, max is ' + _maxLength + ')
for transport ' + this.getType() :
+ 'Bayeux message too big (' + urlLength + ' bytes, max
is ' + _maxLength + ') ' +
+ 'for transport ' + this.getType();
+ // Keep the semantic of calling response callbacks
asynchronously after the request
+ _setTimeout(function()
+ {
+ self.transportFailure(envelope, request, 'error', x);
+ }, 0);
+ }
+ else
+ {
+ try
+ {
+ var sameStack = true;
+ this.jsonpSend({
+ transport: this,
+ url: envelope.url,
+ sync: envelope.sync,
+ headers: _requestHeaders,
+ body: messages,
+ onSuccess: function(response)
+ {
+ self.transportSuccess(envelope, request, response);
+ },
+ onError: function(reason, exception)
+ {
+ if (sameStack)
+ {
+ // Keep the semantic of calling response
callbacks asynchronously after the request
+ _setTimeout(function()
+ {
+ self.transportFailure(envelope, request,
reason, exception);
+ }, 0);
+ }
+ else
+ {
+ self.transportFailure(envelope, request,
reason, exception);
+ }
+ }
+ });
+ sameStack = false;
+ }
+ catch (xx)
+ {
+ // Keep the semantic of calling response callbacks
asynchronously after the request
+ _setTimeout(function()
+ {
+ self.transportFailure(envelope, request, 'error', xx);
+ }, 0);
+ }
+ }
+ };
+ };
+ org.cometd.CallbackPollingTransport.prototype = new org.cometd.Transport();
+ org.cometd.CallbackPollingTransport.prototype.constructor =
org.cometd.CallbackPollingTransport;
+};
diff --git a/tests/features/camel-cometd/src/test/resources/webapp/index.html
b/tests/features/camel-cometd/src/test/resources/webapp/index.html
new file mode 100644
index 00000000..25299612
--- /dev/null
+++ b/tests/features/camel-cometd/src/test/resources/webapp/index.html
@@ -0,0 +1,49 @@
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+
+-->
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html>
+
+<head>
+<title>My Simple Ajax Example</title>
+
+<script type="text/javascript" src="dojo/dojo/dojo.js"></script>
+<script type="text/javascript">
+
+ dojo.require("dojox.cometd");
+
+ function setupComet(){
+ dojox.cometd.init('/cometd');
+ dojox.cometd.subscribe("/channel/test", callback);
+ }
+
+ dojo.addOnLoad(setupComet);
+ dojo.addOnUnload(dojox.cometd, "disconnect");
+
+ function callback(msg){
+ dojo.byId("MSG").innerHTML += msg.data + "\n";
+ }
+
+ </script>
+
+</head>
+<body>
+<h1>CAMEL TEST PAGE</h1>
+<pre id="MSG" />
+</body>
+</html>
diff --git a/tests/features/camel-cometd/src/test/resources/webapp/inout.html
b/tests/features/camel-cometd/src/test/resources/webapp/inout.html
new file mode 100644
index 00000000..9c00e484
--- /dev/null
+++ b/tests/features/camel-cometd/src/test/resources/webapp/inout.html
@@ -0,0 +1,58 @@
+<!--
+
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You 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.
+
+-->
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html>
+
+<head>
+<title>My Simple Ajax Example</title>
+
+<script type="text/javascript" src="dojo/dojo/dojo.js"></script>
+<script type="text/javascript">
+
+ dojo.require("dojox.cometd");
+
+ function setupComet(){
+ dojox.cometd.init('/cometd');
+ dojox.cometd.subscribe("/service/test", callback);
+ dojox.cometd.addListener("/meta/handshake", function(message) {
+ if(message.successful) {
+ dojo.byId("send").onclick = function() {
+ dojox.cometd.publish("/service/test", "Hello");
+ return false;
+ }
+ }
+ });
+ }
+
+ dojo.addOnLoad(setupComet);
+ dojo.addOnUnload(dojox.cometd, "disconnect");
+
+ function callback(msg){
+ dojo.byId("MSG").innerHTML += msg.data + "\n";
+ }
+
+ </script>
+
+</head>
+<body>
+<h1>CAMEL TEST PAGE</h1>
+<button id="send">Send message</button>
+<pre id="MSG" />
+</body>
+</html>
diff --git a/tests/features/pom.xml b/tests/features/pom.xml
index 3e144cb0..a445df26 100644
--- a/tests/features/pom.xml
+++ b/tests/features/pom.xml
@@ -58,6 +58,7 @@
<module>camel-caffeine</module>
<module>camel-cbor</module>
<module>camel-coap</module>
+ <module>camel-cometd</module>
<module>camel-core</module>
<module>camel-couchdb</module>
<module>camel-crypto</module>