/*
* Copyright (c) 2008-2017 the original author or authors.
*
* 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.cometd.javascript;
import java.util.HashMap;
import java.util.Map;
import org.cometd.annotation.RemoteCall;
import org.cometd.annotation.ServerAnnotationProcessor;
import org.cometd.annotation.Service;
import org.cometd.bayeux.BinaryData;
import org.cometd.server.AbstractServerTransport;
import org.cometd.server.JettyJSONContextServer;
import org.cometd.server.ext.BinaryExtension;
import org.eclipse.jetty.util.ajax.JSON;
import org.junit.Assert;
import org.junit.Test;
public class CometDRemoteCallTest extends AbstractCometDTest {
@Test
public void testRemoteCallWithResult() throws Exception {
ServerAnnotationProcessor processor = new ServerAnnotationProcessor(cometdServlet.getBayeux());
String response = "response";
processor.process(new RemoteCallWithResultService(response));
defineClass(Latch.class);
evaluateScript("cometd.init({ url: '" + cometdURL + "', logLevel: '" + getLogLevel() + "' });");
Thread.sleep(1000); // Wait for /meta/connect
evaluateScript("var latch = new Latch(1);");
Latch latch = get("latch");
String request = "request";
evaluateScript("cometd.remoteCall('" + RemoteCallWithResultService.CHANNEL + "', '" + request + "', " +
"function(response)" +
"{" +
" window.assert(response.successful === true, 'missing successful field');" +
" window.assert(response.data === '" + response + "', 'wrong response');" +
" latch.countDown();" +
"});");
Assert.assertTrue(latch.await(5000));
disconnect();
}
@Service
public static class RemoteCallWithResultService {
private static final String CHANNEL = "/remote_result";
private final String response;
public RemoteCallWithResultService(String response) {
this.response = response;
}
@RemoteCall(CHANNEL)
public void service(RemoteCall.Caller caller, Object data) {
caller.result(response);
}
}
@Test
public void testRemoteCallWithFailure() throws Exception {
ServerAnnotationProcessor processor = new ServerAnnotationProcessor(cometdServlet.getBayeux());
String failure = "response";
processor.process(new RemoteCallWithFailureService(failure));
defineClass(Latch.class);
evaluateScript("cometd.init({ url: '" + cometdURL + "', logLevel: '" + getLogLevel() + "' });");
Thread.sleep(1000); // Wait for /meta/connect
evaluateScript("var latch = new Latch(1);");
Latch latch = get("latch");
String request = "request";
evaluateScript("cometd.remoteCall('" + RemoteCallWithFailureService.CHANNEL + "', '" + request + "', " +
"function(response)" +
"{" +
" window.assert(response.successful === false, 'missing successful field');" +
" window.assert(response.data === '" + failure + "', 'wrong response');" +
" latch.countDown();" +
"});");
Assert.assertTrue(latch.await(5000));
disconnect();
}
@Service
public static class RemoteCallWithFailureService {
private static final String CHANNEL = "/remote_failure";
private final String failure;
public RemoteCallWithFailureService(String failure) {
this.failure = failure;
}
@RemoteCall(CHANNEL)
public void service(RemoteCall.Caller caller, Object data) {
caller.failure(failure);
}
}
@Test
public void testRemoteCallTimeout() throws Exception {
long timeout = 1000;
ServerAnnotationProcessor processor = new ServerAnnotationProcessor(cometdServlet.getBayeux());
boolean processed = processor.process(new RemoteCallTimeoutService(timeout));
Assert.assertTrue(processed);
defineClass(Latch.class);
evaluateScript("cometd.init({ url: '" + cometdURL + "', logLevel: '" + getLogLevel() + "' });");
Thread.sleep(1000); // Wait for /meta/connect
evaluateScript("var latch = new Latch(1);");
Latch latch = get("latch");
evaluateScript("" +
"var calls = 0;" +
"cometd.remoteCall('" + RemoteCallTimeoutService.CHANNEL + "', {}, " + timeout + ", " +
"function(response)" +
"{" +
" ++calls;" +
" window.assert(response.successful === false, 'missing successful field');" +
" window.assert(response.error !== undefined, 'missing error field');" +
" latch.countDown();" +
"});");
// Wait enough for the server to actually reply,
// to verify that the callback is not called twice.
Thread.sleep(3 * timeout);
Assert.assertTrue(latch.await(5000));
Assert.assertEquals(1, ((Number)get("calls")).intValue());
disconnect();
}
@Service
public static class RemoteCallTimeoutService {
public static final String CHANNEL = "remote_timeout";
private final long timeout;
public RemoteCallTimeoutService(long timeout) {
this.timeout = timeout;
}
@RemoteCall(CHANNEL)
public void service(final RemoteCall.Caller caller, Object data) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2 * timeout);
caller.result(new HashMap());
} catch (InterruptedException x) {
caller.failure(x);
}
}
}).start();
}
}
@Test
public void testRemoteCallWithCustomDataClass() throws Exception {
JettyJSONContextServer jsonContext = (JettyJSONContextServer)bayeuxServer.getOption(AbstractServerTransport.JSON_CONTEXT_OPTION);
jsonContext.getJSON().addConvertor(Custom.class, new CustomConvertor());
final String request = "request";
final String response = "response";
ServerAnnotationProcessor processor = new ServerAnnotationProcessor(cometdServlet.getBayeux());
boolean processed = processor.process(new RemoteCallWithCustomDataClassService(request, response));
Assert.assertTrue(processed);
defineClass(Latch.class);
evaluateScript("var latch = new Latch(1);");
Latch latch = get("latch");
evaluateScript("cometd.init({ url: '" + cometdURL + "', logLevel: '" + getLogLevel() + "' });");
Thread.sleep(1000); // Wait for /meta/connect
evaluateScript("" +
"cometd.remoteCall('" + RemoteCallWithCustomDataClassService.CHANNEL + "', {" +
" class: '" + Custom.class.getName() + "'," +
" payload: '" + request + "'," +
"}, " +
"function(response)" +
"{" +
" window.assert(response.successful === true);" +
" window.assert(response.data !== undefined);" +
" window.assert(response.data.payload == '" + response + "');" +
" latch.countDown();" +
"});");
Assert.assertTrue(latch.await(5000));
disconnect();
}
@Service
public static class RemoteCallWithCustomDataClassService {
public static final String CHANNEL = "/custom_data_class";
private String request;
private String response;
public RemoteCallWithCustomDataClassService(String request, String response) {
this.request = request;
this.response = response;
}
@RemoteCall(CHANNEL)
public void service(RemoteCall.Caller caller, Custom custom) {
if (request.equals(custom.payload)) {
caller.result(new Custom(response));
} else {
caller.failure("failed");
}
}
}
public static class Custom {
public final String payload;
public Custom(String payload) {
this.payload = payload;
}
}
private class CustomConvertor implements JSON.Convertor {
@Override
public void toJSON(Object obj, JSON.Output out) {
Custom custom = (Custom)obj;
out.add("payload", custom.payload);
}
@Override
public Object fromJSON(Map object) {
return new Custom((String)object.get("payload"));
}
}
@Test
public void testRemoteCallBinary() throws Exception {
bayeuxServer.addExtension(new BinaryExtension());
provideBinaryExtension();
ServerAnnotationProcessor processor = new ServerAnnotationProcessor(bayeuxServer);
String response = "response";
processor.process(new RemoteCallBinaryService());
defineClass(Latch.class);
evaluateScript("cometd.init({ url: '" + cometdURL + "', logLevel: '" + getLogLevel() + "' });");
Thread.sleep(1000); // Wait for /meta/connect
evaluateScript("var latch = new Latch(1);");
Latch latch = get("latch");
evaluateScript("" +
"var buffer = new ArrayBuffer(16);" +
"var view1 = new DataView(buffer);" +
"for (var d = 0; d < view1.byteLength; ++d) {" +
" view1.setUint8(d, d);" +
"}" +
"var meta = {" +
" contentType: 'application/octet-stream'" +
"};" +
"cometd.remoteCallBinary('" + RemoteCallBinaryService.CHANNEL + "', view1, true, meta, function(response) {" +
" window.assert(response.successful === true, 'missing successful field');" +
" var data = response.data;" +
" window.assert(meta.contentType === data.meta.contentType);" +
" var view2 = new DataView(data.data);" +
" for (var i = 0; i < view1.byteLength; ++i) {" +
" window.assert(view2.getUint8(i) === view1.getUint8(i));" +
" }" +
" latch.countDown();" +
"});");
Assert.assertTrue(latch.await(5000));
disconnect();
}
@Service
public static class RemoteCallBinaryService {
public static final String CHANNEL = "/binary";
@RemoteCall(CHANNEL)
public void onBinary(RemoteCall.Caller caller, BinaryData data) {
caller.result(data);
}
}
}