Can I somehow use doAnswer() when an exception is thrown?
I'm using this in my integration test to get method invocations and the test in configured the #RabbitListenerTest...
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyIT {
#Autowired
private RabbitTemplate rabbitTemplate;
#Autowired
private MyRabbitListener myRabbitListener;
#Autowired
private RabbitListenerTestHarness harness;
#Test
public void testListener() throws InterruptedException {
MyRabbitListener myRabbitListener = this.harness.getSpy("event");
assertNotNull(myRabbitListener);
final String message = "Test Message";
LatchCountDownAndCallRealMethodAnswer answer = new LatchCountDownAndCallRealMethodAnswer(1);
doAnswer(answer).when(myRabbitListener).event(message);
rabbitTemplate.convertAndSend("exchange", "key", message);
assertTrue(answer.getLatch().await(20, TimeUnit.SECONDS));
verify(myRabbitListener).messageReceiver(message);
}
#Configuration
#RabbitListenerTest
public static class Config {
#Bean
public MyRabbitListener myRabbitListener(){
return new MyRabbitListener();
}
}
}
It works ok but when I introduce an Exception being thrown, It doesn't i.e
This works
#RabbitListener(id = "event", queues = "queue-name")
public void event(String message) {
log.info("received message > " + message);
}
This doesn't
#RabbitListener(id = "event", queues = "queue-name")
public void event(String message) {
log.info("received message > " + message);
throw new ImmediateAcknowledgeAmqpException("Invalid message, " + message);
}
Any help appreciated
The LatchCountDownAndCallRealMethodAnswer is very basic
#Override
public Void answer(InvocationOnMock invocation) throws Throwable {
invocation.callRealMethod();
this.latch.countDown();
return null;
}
You can copy it to a new class and change it to something like
private volatile Exception exeption;
#Override
public Void answer(InvocationOnMock invocation) throws Throwable {
try {
invocation.callRealMethod();
}
catch (RuntimeException e) {
this.exception = e;
throw e;
}
finally {
this.latch.countDown();
}
return null;
}
public Exception getException() {
return this.exception;
}
then
assertTrue(answer.getLatch().await(20, TimeUnit.SECONDS));
assertThat(answer.getException(), isInstanceOf(ImmediateAcknowledgeAmqpException.class));
Please open a github issue; the framework should support this out-of-the-box.
Related
The goal is :develop a custom Kafka connector that read ,messages from the websocket in a loop method. I try to give you an example on what I've realized:
I create an interface IWebsocketClientEndpoint
public interface IWebsocketClientEndpoint {
IWebsocketClientEndpoint Connect() ;
void Disconnect() throws IOException;
IWebsocketClientEndpoint addMessageHandler(IMessageHandler msgHandler);
void SendMessage(String message) throws Exception;
void SendMessage(ByteBuffer message) throws Exception;
void SendMessage(Object message) throws Exception;
boolean isOpen();
void Dispose()throws IOException;
}
and a class that implement above interface:
#ClientEndpoint
public class WebsocketClientEndpoint implements IWebsocketClientEndpoint {
private WebSocketContainer _container;
private Session _userSession = null;
private IMessageHandler _messageHandler;
private URI _endpointURI;
private WebsocketClientEndpoint(URI endpointURI) {
try {
_endpointURI = endpointURI;
_container = ContainerProvider.getWebSocketContainer();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private WebsocketClientEndpoint(URI endpointURI, int bufferSize) {
try {
_endpointURI = endpointURI;
_container = ContainerProvider.getWebSocketContainer();
_container.setDefaultMaxBinaryMessageBufferSize(bufferSize);
_container.setDefaultMaxTextMessageBufferSize(bufferSize);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static IWebsocketClientEndpoint Create(URI endpointURI){
return new WebsocketClientEndpoint(endpointURI);
}
public static IWebsocketClientEndpoint Create(URI endpointURI,int bufferSize){
return new WebsocketClientEndpoint(endpointURI,bufferSize);
}
public IWebsocketClientEndpoint Connect() {
try {
_container.connectToServer(this, _endpointURI);
return this;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
#OnOpen
public void onOpen(Session userSession) {
this._userSession = userSession;
if (this._messageHandler != null) {
this._messageHandler.handleOpen("Web socket "+ _endpointURI +" opened");}
}
#OnClose
public void onClose(Session userSession, CloseReason reason) {
this._userSession = null;
if (this._messageHandler != null) {
this._messageHandler.handleClose("Web socket "+ _endpointURI +" closed. Reason: " + reason.getReasonPhrase());}
}
public void Disconnect() throws IOException {
CloseReason reason = new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE,"Web socket closed by user");
this._userSession.close(reason);
this._userSession = null;
//close notification to caller
if (this._messageHandler != null) {
this._messageHandler.handleClose("Web socket "+ _endpointURI +" closed. Reason: " + reason.getReasonPhrase());}
}
#Override
public IWebsocketClientEndpoint addMessageHandler(IMessageHandler msgHandler) {
this._messageHandler = msgHandler;
return this;
}
#OnMessage
public void onMessage(String message) {
if (this._messageHandler != null) {
this._messageHandler.handleMessage(message);
}
}
#OnMessage
public void onMessage(ByteBuffer bytes) {
if (this._messageHandler != null) {
this._messageHandler.handleMessage(bytes);
}
}
public void SendMessage(String message) throws Exception {
try{
this._userSession.getAsyncRemote().sendText(message);
}catch (Exception ex){
throw ex;
}
}
public void SendMessage(ByteBuffer message) throws Exception {
try{
this._userSession.getAsyncRemote().sendBinary(message);
}catch (Exception ex){
throw ex;
}
}
public void SendMessage(Object message) throws Exception {
this._userSession.getAsyncRemote().sendObject(message);
}catch (Exception ex){
throw ex;
}
}
#Override
public boolean isOpen() {
if (this._userSession != null){
return this._userSession.isOpen();
}
return false;
}
}
The class WebsocketClientEndpoint is dedicated to the creation of websocket and manage of connection, disconnection, send and receive message.
The goal is: how can I adapt the my websocket structure in the kafka connect structure? I could queue the message received ("public void handleMessage(String s)) from the socket in a ConcurrentLinkedQueue, and then, in the kafka connect loop method, unqueue them. But is it the best solution?
Below, the implementation of my Kafka custom connector
My kafka Connector
public class MySourceTask extends SourceTask {
IWebsocketClientEndpoint _clientEndPoint;
#Override
public void start(Map<String, String> props) {
_clientEndPoint = WebsocketClientEndpoint
.Create(new URI(socket))
.Connect();
_clientEndPoint.addMessageHandler(new IMessageHandler() {
#Override
public void handleMessage(String s) {
}
#Override
public void handleMessage(ByteBuffer byteBuffer) {
}
#Override
public void handleClose(String s) {
}
#Override
public void handleOpen(String s) {
}
});
}
#Override
public List<SourceRecord> poll() throws InterruptedException {
return null;
}
#Override
public void stop() {
_clientEndPoint.Dispose();
}
}
Thanks in advance to anyone
I'd suggest adding the interface to the class
extends SourceTask implements IMessageHandler
Then
_clientEndPoint.addMessageHandler(this);
And when you implement handleMessage, add those strings to some queue. Inside the poll method, you would pop data off that queue to create SourceRecord objects to return.
Inside of stop, call this.handleClose and clean up other resources.
i've been struggeling with the following code. and am not sure how to deserialize it or even pass the correct type at run time.
the code is:
#Override
public <T, R> R sendAsync(T payload, String routingKey, String exchangeName) {
ListenableFuture<R> listenableFuture =
asyncRabbitTemplate.convertSendAndReceiveAsType(
exchangeName,
routingKey,
payload,
new ParameterizedTypeReference<>() {
}
);
try {
return listenableFuture.get();
} catch (InterruptedException | ExecutionException e) {
LOGGER.error(" [x] Cannot get response.", e);
return null;
}
}
let us say that am just calling the method like the following
SaveImageResponse response = backendClient.sendAsync( new SaveImageRequest(createQRRequest.getOwner(), qr), RabbitConstants.CREATE_QR_IMAGE_KEY, RabbitConstants.CDN_EXCHANGE);
while the pojo is the following:
public class SaveImageResponse {
private String id;
private String message;
public SaveImageResponse() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
#Override
public String toString() {
return "SaveImageResponse{" +
"id='" + id + '\'' +
", message='" + message + '\'' +
'}';
}
}
the current code is throwing the following error:
Caused by: java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class dev.yafatek.qr.api.responses.SaveImageResponse (java.util.LinkedHashMap is in module java.base of loader 'bootstrap'; dev.yafatek.qr.api.responses.SaveImageResponse is in unnamed module of loader 'app')
thanks in advance
SOLUTION:
so I ended up using the following:
#Override
public <T, R> R sendAsync(T payload, String routingKey, String exchangeName, Class<R> clazz) {
ListenableFuture<R> listenableFuture =
asyncRabbitTemplate.convertSendAndReceiveAsType(
exchangeName,
routingKey,
payload,
new ParameterizedTypeReference<>() {
}
);
try {
return objectMapper.convertValue(listenableFuture.get(), clazz);
} catch (InterruptedException | ExecutionException e) {
LOGGER.error(" [x] Cannot get response.", e);
return null;
}
}
by using the object mapper and pass the actual type when call the method using
Class<POJO> clazz
to use the above code :
WebsiteInfoResponse websiteInfoResponse = backendClient.sendAsync(new GetWebsiteInfoReq(createBusinessDetailsRequest.getWebsiteUrlId()), RabbitConstants.GET_WEBSITE_INFO_KEY, RabbitConstants.QR_EXCHANGE, WebsiteInfoResponse.class);
You can't.
The whole reason for ParameterizedTypeReference<Foo> is to tell the converter you want a Foo; this has to be resolved at compile time for the method; you can't call sendAsync() to receive different types.
Providing no generic type means it will convert to Object (usually a map).
Even new ParameterizedTypeReference<R>() { } won't work because R is not resolved at compile time for the generic type (of the sendAsync() method).
You have to do the conversion yourself.
#SpringBootApplication
public class So69299112Application {
public static void main(String[] args) {
SpringApplication.run(So69299112Application.class, args);
}
#Bean
MessageConverter converter() {
return new Jackson2JsonMessageConverter();
}
ObjectMapper mapper = new ObjectMapper();
#Bean
AsyncRabbitTemplate template(RabbitTemplate template) {
template.setMessageConverter(new SimpleMessageConverter());
return new AsyncRabbitTemplate(template);
}
#Bean
ApplicationRunner runner(Service service) {
return args -> {
byte[] response = service.sendAsync("bar", "foo", "");
Foo foo = this.mapper.readerFor(Foo.class).readValue(response);
System.out.println(foo);
};
}
#RabbitListener(queues = "foo")
public Foo listen(String in) {
return new Foo(in);
}
public static class Foo {
String foo;
public Foo() {
}
public Foo(String foo) {
this.foo = foo;
}
public String getFoo() {
return this.foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
#Override
public String toString() {
return "Foo [foo=" + this.foo + "]";
}
}
}
#Component
class Service {
private static final Logger LOGGER = LoggerFactory.getLogger(Service.class);
AsyncRabbitTemplate asyncRabbitTemplate;
public Service(AsyncRabbitTemplate asyncRabbitTemplate) {
this.asyncRabbitTemplate = asyncRabbitTemplate;
}
public byte[] sendAsync(Object payload, String routingKey, String exchangeName) {
ListenableFuture<byte[]> listenableFuture = asyncRabbitTemplate.convertSendAndReceive(
exchangeName,
routingKey,
payload);
try {
return listenableFuture.get();
}
catch (InterruptedException | ExecutionException e) {
LOGGER.error(" [x] Cannot get response.", e);
return null;
}
}
}
I have the following working socket server configuration, and would like to add a handler if any exception occurs, eg inside the Deserializer during read of the message.
Therefore I added a #ServiceActivator(inputChannel = "errorChannel"). But the method is never invoked. Why?
#MessageEndpoint
public class SocketEndpoint {
#ServiceActivator(inputChannel = "mainChannel")
public String handleMessage(String message) {
return "normal response";
}
#ServiceActivator(inputChannel = "errorChannel")
public String handleError(MessagingException message) {
//TODO this is never invoked!
return "some error";
}
}
#Bean
public TcpInboundGateway mainGateway(
#Qualifier("tcpFactory") TcpConnectionFactoryFactoryBean factory,
#Qualifier("mainChannel") MessageChannel mainChannel,
#Qualifier("errorChannel") MessageChannel errorChannel) throws Exception {
TcpInboundGateway g = new TcpInboundGateway();
g.setConnectionFactory(factory.getObject());
g.setRequestChannel(mainChannel);
g.setErrorChannel(errorChannel);
return g;
}
#Bean
public TcpConnectionFactoryFactoryBean fact() {
TcpConnectionFactoryFactoryBean f = new TcpConnectionFactoryFactoryBean();
f.setType("server");
//....
f.setDeserializer(new MyDeserializer());
return f;
}
class MyDeserializer implements Deserializer<String> {
#Override
public String deserialize(InputStream inputStream)
throw new RuntimeException("catch me in error-channel");
}
}
throw new RuntimeException("catch me in error-channel");
It can't go to the error channel since there's no message yet (messages sent to error channels are messages that fail downstream processing).
The standard deserializers (that extend AbstractByteArraySerializer) publish a TcpDeserializationExceptionEvent when deserialization fails. See the ByteArrayCrLfSerializer for an example:
https://github.com/spring-projects/spring-integration/blob/master/spring-integration-ip/src/main/java/org/springframework/integration/ip/tcp/serializer/ByteArrayCrLfSerializer.java#L78
public int fillToCrLf(InputStream inputStream, byte[] buffer) throws IOException {
int n = 0;
int bite;
if (logger.isDebugEnabled()) {
logger.debug("Available to read: " + inputStream.available());
}
try {
...
}
catch (SoftEndOfStreamException e) {
throw e;
}
catch (IOException e) {
publishEvent(e, buffer, n);
throw e;
}
catch (RuntimeException e) {
publishEvent(e, buffer, n);
throw e;
}
}
See the documentation. The Deserializer needs to be a bean so that it gets an event publisher.
You can then listen for the event(s) with an ApplicationListener< TcpDeserializationExceptionEvent> or an #EventListener method.
My project uses spring framework
WebSocketConfig.java
#Configuration
#EnableWebMvc
#EnableWebSocket
public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(systemWebSocketHandler(),"/webSocketServer").addInterceptors(new WebSocketHandshakeInterceptor());
registry.addHandler(systemWebSocketHandler(), "/sockjs/webSocketServer").addInterceptors(new WebSocketHandshakeInterceptor())
.withSockJS();
}
#Bean
public WebSocketHandler systemWebSocketHandler(){
return new SystemWebSocketHandler();
}
}
SystemWebSocketHandler.java
public class SystemWebSocketHandler implements WebSocketHandler {
private static final Logger logger;
private static final ArrayList<WebSocketSession> users;
static {
users = new ArrayList<>();
logger = LoggerFactory.getLogger(SystemWebSocketHandler.class);
}
#Autowired
private WebSocketService webSocketService;
#Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
logger.debug("connect to the websocket success......");
users.add(session);
String userName = (String) session.getAttributes().get(Constants.WEBSOCKET_USERNAME);
//查询未读消息
int count = webSocketService.getUnReadNews((String)session.getAttributes().get(Constants.WEBSOCKET_USERNAME));
session.sendMessage(new TextMessage(count+""));
}
#Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
}
#Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
logger.debug("websocket connection closed......");
users.remove(session);
}
#Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
logger.debug("websocket connection closed......");
users.remove(session);
}
#Override
public boolean supportsPartialMessages() {
return false;
}
/**
* 给所有在线用户发送消息
*
* #param message
*/
public void sendMessageToUsers(TextMessage message) {
for (WebSocketSession user : users) {
try {
if (user.isOpen()) {
user.sendMessage(message);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 给某个用户发送消息
*
* #param userName
* #param message
*/
public void sendMessageToUser(String userName, TextMessage message) {
for (WebSocketSession user : users) {
if (user.getAttributes().get(Constants.WEBSOCKET_USERNAME).equals(userName)) {
try {
if (user.isOpen()) {
user.sendMessage(message);
}
} catch (IOException e) {
e.printStackTrace();
}
break;
}
}
}
}
my jsp client
if ('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8080/Origami/webSocketServer");
} else if ('MozWebSocket' in window) {
websocket = new MozWebSocket("ws://localhost:8080/Origami/webSocketServer");
} else {
websocket = new SockJS("http://localhost:8080/Origami/sockjs/webSocketServer");
}
this is my websocket code and it works well
now I want to send messages to the client in a controller ,this is my controller
#Controller
public class AdminController {
static Logger logger = LoggerFactory.getLogger(AdminController.class);
#Autowired(required = false)
private AdminService adminService;
#Autowired(required = false)
private SystemWebSocketHandler systemWebSocketHandler;
#RequestMapping("/auditing")
#ResponseBody
public String auditing(HttpServletRequest request){
String result = "fail";
int id = Integer.parseInt(request.getParameter("id"));
String reason = request.getParameter("reason");
String title = request.getParameter("title");
String username = request.getParameter("username");
News news = new News();
DateTime dateTime = DateTime.now();
news.setNewsTime(dateTime.toDate());
news.setState(0);
news.setUsername(username);
if(reason.equals("")){
result = adminService.auditingById(id,"Y");
news.setNewsContent(String.format(Constants.AUDIT_MESSAGE, username, title, reason));
adminService.addNewsWithUnAudit(news);
}else{
news.setNewsContent(String.format(Constants.UN_AUDIT_MESSAGE,username,title,reason));
result = adminService.addNewsWithUnAudit(news);
result = adminService.auditingById(id, "D");
}
//SystemServerEndPoint serverEndPoint = new SystemServerEndPoint();
int unReadNewsCount = adminService.getUnReadNews(username);
systemWebSocketHandler.sendMessageToUser(username, new TextMessage(unReadNewsCount + ""));
return result;
}
}
I want to call
systemWebSocketHandler.sendMessageToUser(username, new TextMessage(unReadNewsCount + ""));
to send message to the client but systemWebSocketHandler is null
How to inject the systemWebSocketHandler to the controller
or some other ideas to complete the required? Such as the server connect to the websocketserver when it need to send message to the client and closed when it finished
My English is poor, but I'm trying to learn
I have resolved the problem
#Controller
public class AdminController {
#Bean
public SystemWebSocketHandler systemWebSocketHandler() {
return new SystemWebSocketHandler();
}
I have this sort of JUnit test:
#Test
public void testNullCheck() {
String res = someMethod();
assertThat("This is the someMethodTest", res, is(notNullValue()));
}
If someMethod() throws an exception I get a stack trace but the "This is the someMethodTest" is not printed as assertThat() is not called. Is there a somewhat elegant JUnit/hamcrest way to print a custom error message? Eventually I want this in a parametrized test to print the parameter for which the test fails. Note, I don't want to test for a specific exception.
You could create an own Rule that replaces the exception:
public class NiceExceptions implements TestRule {
public Statement apply(final Statement base, final Description description) {
return new Statement() {
#Override
public void evaluate() throws Throwable {
try {
base.evaluate();
} catch (AssumptionViolatedException e) {
throw e;
} catch (Throwable t) {
throw new YourNiceException(t);
}
}
};
}
}
public class YourTest {
#Rule
public final TestRule niceExceptions = new NiceExceptions();
#Test
public void yourTest() {
...
}
}
What about this way:
#Test
public void testNullCheck() {
try{
String res = someMethod();
assertThat("This is the someMethodTest", res, is(notNullValue()));
}catch( Exception e /*or any especific exception*/ ){
fail("This is the someMethodTest Error " + e.getMessage() );
}
}
Using Stefan Birkner's suggestion this is what I came up with. Comments welcome.
package my.test;
import org.junit.internal.AssumptionViolatedException;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
public class ExceptionCatcher implements TestRule {
String msg;
#Override
public Statement apply(final Statement base, final Description description) {
return new Statement() {
#Override
public void evaluate() throws Throwable {
try {
base.evaluate();
} catch (AssumptionViolatedException e) {
throw e;
} catch (AssertionError e){
throw e;
} catch (Throwable t) {
msg = t.getMessage() + "; " + msg;
Throwable cause = t.getCause();
if (cause == null)
cause = t;
StackTraceElement[] stackTrace = cause.getStackTrace();
Throwable t1 = null;
try {
t1 = t.getClass().newInstance();
t1 = t.getClass().getDeclaredConstructor(String.class).newInstance(msg);
t1 = t.getClass().getDeclaredConstructor(String.class, Throwable.class).newInstance(msg, t);
t1.setStackTrace(stackTrace);
throw t1;
} catch (Throwable ignore) {
t1.setStackTrace(stackTrace);
throw t1;
}
}
}
};
}
public void setMsg(String msg) {
this.msg = msg;
}
}
And in the test case:
#Rule
public final ExceptionCatcher catcher = new ExceptionCatcher();
#Before
public void setUp() throws Exception {
catcher.setMsg("....");
}
#Test
public void testNullCheck() {
String res = someMethod();
assertThat("This is the someMethodTest", res, is(notNullValue()));
}