/* * Copyright (C) 2010 Red Hat, Inc. and/or its affiliates. * * 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.jboss.errai.bus.client.tests; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.jboss.errai.bus.client.ErraiBus; import org.jboss.errai.bus.client.api.BusErrorCallback; import org.jboss.errai.bus.client.api.Subscription; import org.jboss.errai.bus.client.api.base.DefaultErrorCallback; import org.jboss.errai.bus.client.api.base.MessageBuilder; import org.jboss.errai.bus.client.api.messaging.Message; import org.jboss.errai.bus.client.api.messaging.MessageCallback; import org.jboss.errai.bus.client.tests.support.GenericServiceB; import org.jboss.errai.bus.client.tests.support.NonPortableException; import org.jboss.errai.bus.client.tests.support.Person; import org.jboss.errai.bus.client.tests.support.RandomProvider; import org.jboss.errai.bus.client.tests.support.SType; import org.jboss.errai.bus.client.tests.support.SpecificEntity; import org.jboss.errai.bus.client.tests.support.SubService; import org.jboss.errai.bus.client.tests.support.TestException; import org.jboss.errai.bus.client.tests.support.TestInterceptorRPCService; import org.jboss.errai.bus.client.tests.support.TestRPCService; import org.jboss.errai.bus.client.tests.support.User; import org.jboss.errai.bus.common.AbstractErraiTest; import org.jboss.errai.common.client.api.ErrorCallback; import org.jboss.errai.common.client.api.RemoteCallback; import org.jboss.errai.common.client.protocols.MessageParts; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.GWT.UncaughtExceptionHandler; import com.google.gwt.user.client.Timer; /** * @author Mike Brock <cbrock@redhat.com> * @author Christian Sadilek <csadilek@redhat.com> */ public class BusCommunicationTests extends AbstractErraiTest { @Override public String getModuleName() { return "org.jboss.errai.bus.ErraiBusTests"; } @Override protected void gwtSetUp() throws Exception { originalHandler = GWT.getUncaughtExceptionHandler(); testHandler = new TestUncaughtExceptionHandler(originalHandler); GWT.setUncaughtExceptionHandler(testHandler); super.gwtSetUp(); } @Override protected void gwtTearDown() throws Exception { GWT.setUncaughtExceptionHandler(originalHandler); for (final Subscription sub : subscriptions) { sub.remove(); } subscriptions.clear(); super.gwtTearDown(); } public void testBasicRoundTrip() { runAfterInit(new Runnable() { @Override public void run() { subscriptions.add(bus.subscribe("MyTestService", new MessageCallback() { @Override public void callback(final Message message) { finishTest(); } })); MessageBuilder.createMessage() .toSubject("ServerEchoService") .with(MessageParts.ReplyTo, "MyTestService") .done().sendNowWith(bus); } }); } private int replies = 0; private UncaughtExceptionHandler originalHandler; private TestUncaughtExceptionHandler testHandler; private final Collection<Subscription> subscriptions = new ArrayList<Subscription>(); public void testBasicRoundTripWithGiantString() { runAfterInit(new Runnable() { @Override public void run() { subscriptions.add(bus.subscribe("GiantStringClient", new MessageCallback() { @Override public void callback(final Message message) { if (++replies == 51) finishTest(); } })); MessageBuilder.createMessage() .toSubject("GiantStringTestService") .with(MessageParts.ReplyTo, "GiantStringClient") .done().sendNowWith(bus); } }); } public void testBasicRoundTripWithoutToSubjectCall() { runAfterInit(new Runnable() { @Override public void run() { subscriptions.add(bus.subscribe("MyTestService", new MessageCallback() { @Override public void callback(final Message message) { finishTest(); } })); MessageBuilder.createMessage("ServerEchoService") .with(MessageParts.ReplyTo, "MyTestService") .done().sendNowWith(bus); } }); } public static class GWTRandomProvider implements RandomProvider { private static final char[] CHARS = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; @Override public boolean nextBoolean() { return com.google.gwt.user.client.Random.nextBoolean(); } @Override public int nextInt(final int upper) { return com.google.gwt.user.client.Random.nextInt(upper); } @Override public double nextDouble() { return com.google.gwt.user.client.Random.nextDouble(); } @Override public char nextChar() { return CHARS[com.google.gwt.user.client.Random.nextInt(1000) % CHARS.length]; } @Override public String randString() { final StringBuilder builder = new StringBuilder(); final int len = nextInt(25) + 5; for (int i = 0; i < len; i++) { builder.append(nextChar()); } return builder.toString(); } } public void testSerializableCase() { runAfterInit(new Runnable() { @Override public void run() { try { final SType sType1 = SType.create(new GWTRandomProvider()); subscriptions.add(bus.subscribe("ClientReceiver", new MessageCallback() { @Override public void callback(final Message message) { final SType type = message.get(SType.class, "SType"); try { assertNotNull("SType is null.", type); compareSTypes(sType1, type); finishTest(); return; } catch (final Throwable e) { throw new AssertionError(e); } } })); MessageBuilder.createMessage() .toSubject("TestService1") .with("SType", sType1) .with(MessageParts.ReplyTo, "ClientReceiver") .done().sendNowWith(bus); } catch (final Throwable t) { t.printStackTrace(System.out); } } }); } private static void compareSTypes(final SType expected, final SType actual) { if (expected == actual) return; if (expected == null || actual == null) fail("Only one was null."); assertEquals(expected.getClass(), actual.getClass()); assertEquals(expected.getSuperValue(), actual.getSuperValue()); assertEquals(expected.getPlace(), actual.getPlace()); assertEquals(expected.getByteValue(), actual.getByteValue()); assertEquals(expected.getCharValue(), actual.getCharValue()); assertApproximatelyEqual(expected.getDoubleValue(), actual.getDoubleValue()); assertApproximatelyEqual(expected.getFloatValue(), actual.getFloatValue()); assertEquals(expected.getIntValue(), actual.getIntValue()); assertEquals(expected.getLongValue(), actual.getLongValue()); assertEquals(expected.getShortValue(), actual.getShortValue()); assertEquals(expected.getActive(), actual.getActive()); assertTrue(Arrays.equals(expected.getCharArray(), actual.getCharArray())); assertEquals(expected.getEndDate(), actual.getEndDate()); assertEquals(expected.getFieldOne(), actual.getFieldOne()); assertEquals(expected.getFieldTwo(), actual.getFieldTwo()); compareListOfSTypes(expected.getListOfStypes(), actual.getListOfStypes()); assertEquals(expected.getListOfDates(), actual.getListOfDates()); compareMapOfSTypes(expected.getMapofStypes(), actual.getMapofStypes()); assertEquals(expected.getsTypeToStype(), actual.getsTypeToStype()); assertEquals(expected.getStartDate(), actual.getStartDate()); compareMultiArray(expected, actual); compareSTypeArray(expected.getsTypeArray(), actual.getsTypeArray()); /* * The assertions above make it easier to see what went wrong, but in case * something ever changes this assertion ought to hold. */ assertEquals(expected, actual); } private static void compareMapOfSTypes(final Map<String, SType> expected, final Map<String, SType> actual) { if (expected == null || actual == null) { assertSame(expected, actual); } else { assertEquals(expected.size(), actual.size()); for (final Entry<String, SType> entry : expected.entrySet()) { assertTrue(actual.containsKey(entry.getKey())); final SType expectedValue = entry.getValue(); final SType actualValue = actual.get(entry.getKey()); if (expectedValue == null || actualValue == null) { assertSame(expectedValue, actualValue); } else { compareSTypes(expectedValue, actualValue); } } } } private static void compareListOfSTypes(final List<SType> expectedList, final List<SType> actualList) throws AssertionError { if (expectedList == null || actualList == null) { assertSame(expectedList, actualList); } else { compareSTypeArray(expectedList.toArray(new SType[0]), actualList.toArray(new SType[0])); } } private static void compareSTypeArray(final SType[] expectedSTypeArray, final SType[] actualSTypeArray) throws AssertionError { if (expectedSTypeArray == null || actualSTypeArray == null) { assertSame(expectedSTypeArray, actualSTypeArray); return; } assertEquals(expectedSTypeArray.length, actualSTypeArray.length); for (int i = 0; i < expectedSTypeArray.length; i++) { try { compareSTypes(expectedSTypeArray[i], actualSTypeArray[i]); } catch (final AssertionError ae) { throw new AssertionError("array[" + i + "] not equal.", ae); } } } private static void compareMultiArray(final SType expected, final SType actual) { final char[][] expectedCharArrayMulti = expected.getCharArrayMulti(); final char[][] actualCharArrayMulti = actual.getCharArrayMulti(); if (expectedCharArrayMulti == null || actualCharArrayMulti == null) { assertSame(expectedCharArrayMulti, actualCharArrayMulti); } else { assertEquals(expectedCharArrayMulti.length, actualCharArrayMulti.length); for (int i = 0; i < expectedCharArrayMulti.length; i++) { assertTrue("array[" + i + "] not equal.", Arrays.equals(expectedCharArrayMulti[i], actualCharArrayMulti[i])); } } } public void testSerializableCase2() { runAfterInit(new Runnable() { @Override public void run() { final User user = User.create(); subscriptions.add(bus.subscribe("ClientReceiver2", new MessageCallback() { @Override public void callback(final Message message) { final User u = message.get(User.class, "User"); try { assertNotNull(u); assertTrue(user.toString().equals(u.toString())); finishTest(); return; } catch (final Throwable t) { t.printStackTrace(); } fail(); } })); MessageBuilder.createMessage() .toSubject("TestService2") .with("User", user) .with(MessageParts.ReplyTo, "ClientReceiver2") .done().sendNowWith(bus); } }); } public void testRpc() { runAfterInit(new Runnable() { @Override public void run() { final TestRPCService remote = MessageBuilder.createCall(new RemoteCallback<Boolean>() { @Override public void callback(final Boolean response) { assertTrue(response); finishTest(); } }, TestRPCService.class); remote.isGreaterThan(10, 5); } }); } /** * Regression test for ERRAI-282 under the CDI implementation of ErraiRPC. * Note that there is a similar test in ErraiCDI, which has a strikingly * similar, yet independent, implementation of ErraiRPC. */ public void testRpcToInheritedMethod() { runAfterInit(new Runnable() { @Override public void run() { final SubService remote = MessageBuilder.createCall(new RemoteCallback<Integer>() { @Override public void callback(final Integer response) { assertNotNull(response); assertEquals(1, (int) response); finishTest(); } }, SubService.class); remote.baseServiceMethod(); // this is a service method inherited from the super interface } }); } public void testRpcThrowingException() { runAfterInit(new Runnable() { @Override public void run() { MessageBuilder.createCall( new RemoteCallback<Object>() { @Override public void callback(final Object response) { } }, new BusErrorCallback() { @Override public boolean error(final Message message, final Throwable caught) { assertNotNull("Message is null.", message); final String additionalDetails = message.get(String.class, MessageParts.AdditionalDetails); assertNotNull("Server-provided stack trace is null.", additionalDetails); assertTrue("Additional detail string did not contain a frame for the RPC call. Message contents:\n\n" + message, additionalDetails.contains("TestRPCServiceImpl.exception(")); assertNotNull("Exception is null.", caught); System.out.println("The exception delivered to the error handler (portable case):"); assertEquals("Received wrong kind of Throwable.", TestException.class, caught.getClass()); assertStackContains("TestRPCServiceImpl.exception(", caught); finishTest(); return false; } }, TestRPCService.class ).exception(); } }); } public void testRpcThrowingNonPortableException() { runAfterInit(new Runnable() { @Override public void run() { MessageBuilder.createCall( new RemoteCallback<Object>() { @Override public void callback(final Object response) { } }, new BusErrorCallback() { @Override public boolean error(final Message message, final Throwable caught) { assertNotNull("Message is null.", message); final String additionalDetails = message.get(String.class, MessageParts.AdditionalDetails); assertNotNull("Server-provided stack trace is null.", additionalDetails); assertTrue("Additional detail string did not contain a frame for the RPC call. Message contents:\n\n" + message, additionalDetails.contains("TestRPCServiceImpl.nonPortableException(")); assertNotNull("Throwable is null.", caught); System.out.println("The exception delivered to the error handler (non-portable case):"); caught.printStackTrace(System.out); try { throw caught; } catch (final Throwable throwable) { assertEquals(NonPortableException.class.getName() + ":" + "message", throwable.getMessage()); finishTest(); } return false; } }, TestRPCService.class ).nonPortableException(); } }); } public void testRpcReturningVoid() { runAfterInit(new Runnable() { @Override public void run() { MessageBuilder.createCall( new RemoteCallback<Void>() { @Override public void callback(final Void response) { finishTest(); } }, TestRPCService.class).returnVoid(); } }); } public void testRpcReturningNull() { runAfterInit(new Runnable() { @Override public void run() { MessageBuilder.createCall( new RemoteCallback<Person>() { @Override public void callback(final Person response) { assertNull(response); finishTest(); } }, TestRPCService.class).returnNull(); } }); } public void testRpcToGenericService() { runAfterInit(new Runnable() { @Override public void run() { final GenericServiceB remote = MessageBuilder.createCall(new RemoteCallback<String>() { @Override public void callback(final String response) { assertNotNull(response); assertEquals("SpecificEntity", response); finishTest(); } }, GenericServiceB.class); remote.create(new SpecificEntity()); } }); } /** * Related to issue: https://issues.jboss.org/browse/ERRAI-318 */ public void testVarArgsToRPC1() { runAfterInit(new Runnable() { @Override public void run() { MessageBuilder.createCall(new RemoteCallback<String>() { @Override public void callback(final String response) { assertEquals("foobar", response); finishTest(); } }, TestRPCService.class).testVarArgs("foo", "bar"); } }); } public void testVarArgsToRPC2() { runAfterInit(new Runnable() { @SuppressWarnings("NullArgumentToVariableArgMethod") @Override public void run() { MessageBuilder.createCall(new RemoteCallback<String>() { @Override public void callback(final String response) { assertEquals("foo", response); finishTest(); } }, TestRPCService.class).testVarArgs("foo", null); } }); } public void testVarArgsToRPC3() { runAfterInit(new Runnable() { @Override public void run() { MessageBuilder.createCall(new RemoteCallback<String>() { @Override public void callback(final String response) { assertEquals("foo", response); finishTest(); } }, TestRPCService.class).testVarArgs("foo"); } }); } public void testMultipleEndpointsOnRemoteService() { runAfterInit(new Runnable() { @Override public void run() { class TestCount { private int countdown; TestCount(final int countdown) { this.countdown = countdown; } public boolean done() { return --countdown == 0; } } final TestCount testCount = new TestCount(2); MessageBuilder.createMessage() .toSubject("TestSvc") .command("bar") .done().repliesTo(new MessageCallback() { @Override public void callback(final Message message) { assertEquals("Bar!", message.get(String.class, "Msg")); if (testCount.done()) { finishTest(); } } }).sendGlobalWith(ErraiBus.get()); MessageBuilder.createMessage() .toSubject("TestSvc") .command("foo") .done().repliesTo(new MessageCallback() { @Override public void callback(final Message message) { assertEquals("Foo!", message.get(String.class, "Msg")); if (testCount.done()) { finishTest(); } } }).sendGlobalWith(ErraiBus.get()); } }); } public void testCommandMessageThrowingException() { testHandler.setExpectedExceptionTypes(RuntimeException.class); runAfterInit(new Runnable() { @Override public void run() { MessageBuilder.createMessage() .toSubject("TestSvc") .command("baz") .errorsHandledBy(new BusErrorCallback() { @Override public boolean error(final Message message, final Throwable throwable) { fail("An exception thrown by a MessageCallback should not be delivered to the caller!"); return false; } }) .repliesTo(new MessageCallback() { @Override public void callback(final Message message) { fail("This service throws an Exception and does not reply. " + "The MessageCallback should not have been invoked."); } }) .sendGlobalWith(ErraiBus.get()); } }); new Timer() { @Override public void run() { finishTest(); } }.schedule(7000); } public void testNonExistingCommandMessage() { testHandler.setExpectedExceptionTypes(RuntimeException.class); runAfterInit(new Runnable() { @Override public void run() { subscriptions.add(bus.subscribe(DefaultErrorCallback.CLIENT_ERROR_SUBJECT, new MessageCallback() { @Override public void callback(final Message message) { final String errorMessage = message.get(String.class, MessageParts.ErrorMessage); final String additionalDetails = message.get(String.class, MessageParts.AdditionalDetails); assertTrue("Throwable should contain non-existing service name." + "\n\tObserved errorMessage: " + errorMessage + "\n\tObserved additionalDetails: " + additionalDetails, (errorMessage + additionalDetails).contains("non-existing")); finishTest(); } })); MessageBuilder.createMessage() .toSubject("TestSvc") .command("non-existing") .done() .repliesTo(new MessageCallback() { @Override public void callback(final Message message) { fail("Callback should not have been invoked!"); } }) .sendGlobalWith(ErraiBus.get()); } }); } public void testInterceptedRpcWithEndpointBypassing() { runAfterInit(new Runnable() { @Override public void run() { MessageBuilder.createCall(new RemoteCallback<String>() { @Override public void callback(final String response) { assertEquals("Request was not intercepted", "intercepted", response); finishTest(); } }, TestRPCService.class) .interceptedRpcWithEndpointBypassing(); } }); } public void testInterceptedRpcWithResultManipulation() { runAfterInit(new Runnable() { @Override public void run() { MessageBuilder.createCall(new RemoteCallback<String>() { @Override public void callback(final String response) { assertEquals("Request was not intercepted", "result_intercepted", response); finishTest(); } }, TestRPCService.class).interceptedRpcWithResultManipulation(); } }); } public void testInterceptedRpcWithParameterManipulation() { runAfterInit(new Runnable() { @Override public void run() { MessageBuilder.createCall(new RemoteCallback<String>() { @Override public void callback(final String response) { assertEquals("Request was not intercepted", "interceptor_value", response); finishTest(); } }, TestRPCService.class).interceptedRpcWithParameterManipulation("value"); } }); } public void testInterceptedRpc1() { runAfterInit(new Runnable() { @Override public void run() { MessageBuilder.createCall(new RemoteCallback<String>() { @Override public void callback(final String response) { assertEquals("Request was not intercepted", "intercepted", response); finishTest(); } }, TestInterceptorRPCService.class) .interceptedRpc1(); } }); } public void testInterceptedRpc2() { runAfterInit(new Runnable() { @Override public void run() { MessageBuilder.createCall(new RemoteCallback<String>() { @Override public void callback(final String response) { assertEquals("Request was not intercepted", "intercepted", response); finishTest(); } }, TestInterceptorRPCService.class) .interceptedRpc2(); } }); } public void testInterceptedRpcWithChainedInterceptors() { runAfterInit(new Runnable() { @Override public void run() { MessageBuilder.createCall(new RemoteCallback<String>() { @Override public void callback(final String response) { assertEquals("Request was not intercepted", "ABCD", response); finishTest(); } }, TestRPCService.class).interceptedRpcWithChainedInterceptors(""); } }); } public void testPlainMessagingWithRpcEndpoint() { runAfterInit(new Runnable() { @Override public void run() { subscriptions.add(bus.subscribe("PlainMessageResponse", new MessageCallback() { @Override public void callback(final Message message) { finishTest(); } })); MessageBuilder.createMessage() .toSubject("TestRPCServiceImpl") .with(MessageParts.ReplyTo, "PlainMessageResponse") .done().sendNowWith(bus); } }); } public void testBusUnsubscribe() { runAfterInit(new Runnable() { @Override public void run() { final String subjectToSubscribe = "testBusUnsubscribeTestSubjectThatWillBeRemovedAndThrowAnException"; final Subscription subs = bus.subscribe(subjectToSubscribe, new MessageCallback() { @Override public void callback(final Message message) { } }); subs.remove(); MessageBuilder.createMessage() .toSubject(subjectToSubscribe) .errorsHandledBy((ErrorCallback<Message>) (m, t) -> { finishTest(); return false; }) .repliesTo(m -> { fail("should have thrown exception because service should have been de-registered"); }) .sendNowWith(bus); } }); } static void assertStackContains(final String string, Throwable t) { while (t != null) { for (final StackTraceElement ste : t.getStackTrace()) { if (ste.toString().contains(string)) { return; } } t = t.getCause(); } fail("Stack trace does not contain the string: " + string); } @SuppressWarnings("rawtypes") static class TestUncaughtExceptionHandler implements UncaughtExceptionHandler { private final UncaughtExceptionHandler originalHandler; final Set<Class> ignorable; public TestUncaughtExceptionHandler(final UncaughtExceptionHandler originalHandler) { this.originalHandler = originalHandler; ignorable = new HashSet<Class>(); } public void setExpectedExceptionTypes(final Class... expectedExceptionTypes) { setExpectedExceptionTypes(Arrays.asList(expectedExceptionTypes)); } public void setExpectedExceptionTypes(final Collection<Class> expectedExceptionTypes) { ignorable.clear(); ignorable.addAll(expectedExceptionTypes); } @Override public void onUncaughtException(final Throwable e) { if (e != null) { if (!ignorable.contains(e.getClass())) { originalHandler.onUncaughtException(e); } } } } }