I have a docker container where I deploy a web application and a ActiveMQ Artemis broker as war files on a single Jetty instance. Communication via TCP is working like expected but through VM protocol nothing happens.
Are the applications separated in some way?
broker.xml:
<connectors>
<connector name="in-vm-connector">vm://0</connector>
<connector name="connector">tcp://localhost:61616</connector>
</connectors>
<acceptors>
<acceptor name="in-vm-acceptor">vm://0</acceptor>
<acceptor name="acceptor">tcp://localhost:61616</acceptor>
</acceptors>
Edit:
Creating the broker
public class EmbeddedBroker implements ServletContextListener {
#Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
SecurityConfiguration securityConfig = new SecurityConfiguration();
securityConfig.addUser("guest", "guest");
securityConfig.addRole("guest", "guest");
securityConfig.setDefaultUser("guest");
ActiveMQJAASSecurityManager securityManager = new ActiveMQJAASSecurityManager(InVMLoginModule.class.getName(), securityConfig);
// Step 2. Create and start embedded broker.
try {
ActiveMQServer server = ActiveMQServers.newActiveMQServer("broker.xml", null, securityManager);
server.start();
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void contextDestroyed(ServletContextEvent sce) {}
}
Creating a connection with the broker
#Bean
ActiveMQConnectionFactory activeMqConnectionFactory(#Value("${" + BROKER_URL + "}") String brokerUrl,
#Value("${" + BROKER_USERNAME + "}") String username,
#Value("${" + BROKER_PASSWORD + "}") String password) {
return new ActiveMQConnectionFactory(brokerUrl, username, password);
}
#Bean
MessageListener messageListener(RequestHandler requestHandler) {
return new JmsChannel(requestHandler);
}
#Bean
DefaultMessageListenerContainer messageListenerContainer(ConnectionFactory connectionFactory,
MessageListener messageListener,
#Value("${" + CONSUMER_QUEUE + "}") String consumerQueueName) {
var listenerContainer = new DefaultMessageListenerContainer();
listenerContainer.setConnectionFactory(connectionFactory);
listenerContainer.setDestinationName(consumerQueueName);
listenerContainer.setMessageListener(messageListener);
listenerContainer.setSessionTransacted(true);
return listenerContainer;
}
#Bean
JmsTemplate jmsTemplate(ConnectionFactory connectionFactory) {
var jmsTemplate = new JmsTemplate(connectionFactory);
jmsTemplate.setReceiveTimeout(10000);
return jmsTemplate;
}
Related
I have installed WMQ JMS resource adapter (9.0.4) to my JBOSS EAP 7 standalone-full.xml & created connection factory and admin object to it.
/subsystem=resource-adapters/resource-adapter=ibm-mq-resource-adapter:add(archive=wmq.jmsra-9.0.4.0.rar, transaction-support=NoTransaction)
/subsystem=resource-adapters/resource-adapter=ibm-mq-resource-adapter/admin-objects=queue-ao1:add(class-name=com.ibm.mq.connector.outbound.MQQueueProxy, jndi-name=java:jboss/outbound)
/subsystem=resource-adapters/resource-adapter=ibm-mq-resource-adapter/admin-objects=queue-ao1/config-properties=baseQueueName:add(value=TEST1)
/subsystem=resource-adapters/resource-adapter=ibm-mq-resource-adapter/admin-objects=queue-ao1/config-properties=baseQueueManagerName:add(value=TESTMANAGER)
Connection definition:
<connection-definition class-name="com.ibm.mq.connector.outbound.ManagedConnectionFactoryImpl" jndi-name="java:jboss/mqSeriesJMSFactoryoutbound" tracking="false" pool-name="mq-cd">
<config-property name="channel">
SYSTEM.DEF.XXX
</config-property>
<config-property name="hostName">
XX-XXX
</config-property>
<config-property name="transportType">
CLIENT
</config-property>
<config-property name="queueManager">
TESTMANAGER
</config-property>
<config-property name="port">
1414
</config-property>
</connection-definition>
In my understanding, If I post a message to the outbound queue from the connection factory mqSeriesJMSFactoryoutbound, I should be able to reach IBM MQ. I tried with below code to look up connection factory but I am getting naming notfound exception. Please help
public class TestQueueConnection {
// Set up all the default values
private static final String DEFAULT_MESSAGE = "Hello, World! successfull";
private static final String DEFAULT_CONNECTION_FACTORY = "java:jboss/mqSeriesJMSFactoryoutbound";
private static final String DEFAULT_DESTINATION = "java:jboss/outbound";
private static final String DEFAULT_MESSAGE_COUNT = "1";
private static final String DEFAULT_USERNAME = "jmsuser";
private static final String DEFAULT_PASSWORD = "jmsuser123";
private static final String INITIAL_CONTEXT_FACTORY = "org.jboss.naming.remote.client.InitialContextFactory";
private static final String PROVIDER_URL = "http-remoting://127.0.0.1:8070";
public static void main(String[] args) throws JMSException {
Context namingContext = null;
try {
String userName = System.getProperty("username", DEFAULT_USERNAME);
String password = System.getProperty("password", DEFAULT_PASSWORD);
// Set up the namingContext for the JNDI lookup
final Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, INITIAL_CONTEXT_FACTORY);
env.put(Context.PROVIDER_URL, System.getProperty(Context.PROVIDER_URL, PROVIDER_URL));
namingContext = new InitialContext(env);
// Perform the JNDI lookups
String connectionFactoryString = System.getProperty("connection.factory", DEFAULT_CONNECTION_FACTORY);
namingContext.lookup(connectionFactoryString);
QueueConnectionFactory connectionFactory = (QueueConnectionFactory)
JMSContext jmsContext = connectionFactory.createContext(DEFAULT_USERNAME, DEFAULT_PASSWORD);
Queue destination = (Queue) namingContext.lookup(DEFAULT_DESTINATION);
jmsContext.createProducer().send(destination, DEFAULT_MESSAGE);
System.out.println("><><><><><><>< MESSAGE POSTED <><><><><><><>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" );
} catch (NamingException e) {
e.printStackTrace();
} finally {
if (namingContext != null) {
try {
namingContext.close();
} catch (NamingException e) {
}
}
}
}
Made couple of changes to the above.
In connection-definition, instead of com.ibm.mq.connector.outbound.ManagedConnectionFactoryImpl, used ManagedQueueConnectionFactoryImpl to avoid class cast exception at runtime.
Connection factories created by RA are not accessible outside of its JVM. Written a servlet to access these connection factory. I am able to connect with below piece of code.
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
response.getWriter().append("Served at: ").append(request.getContextPath());
Context namingContext = null;
String connectionFactoryString = "mqSeriesJMSFactoryoutbound";
String queueName = "outbound";
MessageProducer producer = null;
Session session = null;
Connection conn =null;
try {
namingContext = new InitialContext();
QueueConnectionFactory connectionFactory = (QueueConnectionFactory) namingContext.lookup(connectionFactoryString);
Queue destination = (Queue) namingContext.lookup(queueName);
conn = connectionFactory.createConnection();
session = conn.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);
producer = session.createProducer(destination);
TextMessage message = session.createTextMessage();
message.setText(msg);
producer.send(message,
Message.DEFAULT_DELIVERY_MODE,
Message.DEFAULT_PRIORITY,
Message.DEFAULT_TIME_TO_LIVE);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally {
// Close the message producer
try {
if (producer != null) producer.close();
}
catch (JMSException e) {
System.err.println("Failed to close message producer: " + e);
}
// Close the session
try {
if (session != null) session.close();
}
catch (JMSException e) {
System.err.println("Failed to close session: " + e);
}
// Close the connection
try {
if(conn != null)
conn.close();
}
catch (JMSException e) {
System.err.println("Failed to close connection: " + e);
}
}
}
Normally, when we define a class-level #KafkaListener and method level #KafkaHandlers, we can define a default #KafkaHandler to handle unexpected payloads.
https://docs.spring.io/spring-kafka/docs/current/reference/html/#class-level-kafkalistener
But, what should we do if we don't have a default method?
With version 2.6 and later, you can configure a SeekToCurrentErrorHandler to immediately send such messages to a dead letter topic, by examining the exception.
Here is a simple Spring Boot application that demonstrates the technique:
#SpringBootApplication
public class So59256214Application {
public static void main(String[] args) {
SpringApplication.run(So59256214Application.class, args);
}
#Bean
public NewTopic topic1() {
return TopicBuilder.name("so59256214").partitions(1).replicas(1).build();
}
#Bean
public NewTopic topic2() {
return TopicBuilder.name("so59256214.DLT").partitions(1).replicas(1).build();
}
#KafkaListener(id = "so59256214.DLT", topics = "so59256214.DLT")
void listen(ConsumerRecord<?, ?> in) {
System.out.println("dlt: " + in);
}
#Bean
public ApplicationRunner runner(KafkaTemplate<String, Object> template) {
return args -> {
template.send("so59256214", 42);
template.send("so59256214", 42.0);
template.send("so59256214", "No handler for this");
};
}
#Bean
ErrorHandler eh(KafkaOperations<String, Object> template) {
SeekToCurrentErrorHandler eh = new SeekToCurrentErrorHandler(new DeadLetterPublishingRecoverer(template));
BackOff neverRetryOrBackOff = new FixedBackOff(0L, 0);
BackOff normalBackOff = new FixedBackOff(2000L, 3);
eh.setBackOffFunction((rec, ex) -> {
if (ex.getMessage().contains("No method found for class")) {
return neverRetryOrBackOff;
}
else {
return normalBackOff;
}
});
return eh;
}
}
#Component
#KafkaListener(id = "so59256214", topics = "so59256214")
class Listener {
#KafkaHandler
void integerHandler(Integer in) {
System.out.println("int: " + in);
}
#KafkaHandler
void doubleHandler(Double in) {
System.out.println("double: " + in);
}
}
spring.kafka.consumer.auto-offset-reset=earliest
spring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.JsonDeserializer
spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer
Result:
int: 42
double: 42.0
dlt: ConsumerRecord(topic = so59256214.DLT, ...
I have a simple netty connection pool and a simple HTTP endpoint to use that pool to send TCP messages to ServerSocket. The relevant code looks like this, the client (NettyConnectionPoolClientApplication) is:
#SpringBootApplication
#RestController
public class NettyConnectionPoolClientApplication {
private SimpleChannelPool simpleChannelPool;
public static void main(String[] args) {
SpringApplication.run(NettyConnectionPoolClientApplication.class, args);
}
#PostConstruct
public void setup() throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group);
bootstrap.channel(NioSocketChannel.class);
bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
bootstrap.remoteAddress(new InetSocketAddress("localhost", 9000));
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
pipeline.addLast(new DummyClientHandler());
}
});
simpleChannelPool = new SimpleChannelPool(bootstrap, new DummyChannelPoolHandler());
}
#RequestMapping("/test/{msg}")
public void test(#PathVariable String msg) throws Exception {
Future<Channel> future = simpleChannelPool.acquire();
future.addListener((FutureListener<Channel>) f -> {
if (f.isSuccess()) {
System.out.println("Connected");
Channel ch = f.getNow();
ch.writeAndFlush(msg + System.lineSeparator());
// Release back to pool
simpleChannelPool.release(ch);
} else {
System.out.println("not successful");
}
});
}
}
and the Server (ServerSocketRunner)
public class ServerSocketRunner {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(9000);
while (true) {
Socket socket = serverSocket.accept();
new Thread(() -> {
System.out.println("New client connected");
try (PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()));) {
String inputLine, outputLine;
out.println("Hello client!");
do {
inputLine = in.readLine();
System.out.println("Received: " + inputLine);
} while (!"bye".equals(inputLine));
System.out.println("Closing connection...");
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
DummyChannelPoolHandler and DummyClientHandler just print out events that happen, so they are not relevant. When the server and the client are started and I send a test message to test endpoint, I can see the server prints "New client connected" but the message sent by client is not printed. None of the consecutive messages sent by client are printed by the server.
If I try telnet, everything works fine, the server prints out messages. Also it works fine with regular netty client with same bootstrap config and without connection pool (SimpleNettyClientApplication).
Can anyone see what is wrong with my connection pool, I'm out of ideas
Netty versioin: 4.1.39.Final
All the code is available here.
UPDATE
Following Norman Maurer advice. I added
ChannelFuture channelFuture = ch
.writeAndFlush(msg + System.lineSeparator());
channelFuture.addListener(writeFuture -> {
System.out
.println("isSuccess(): " + channelFuture.isSuccess() + " : " + channelFuture.cause());
});
This prints out
isSuccess: false : java.lang.UnsupportedOperationException: unsupported message type: String (expected: ByteBuf, FileRegion)
To fix it, I just converted String into ByteBuf
ch.writeAndFlush(Unpooled.wrappedBuffer((msg + System.lineSeparator()).getBytes()));
You should check what the status of the ChannelFuture is that is returned by writeAndFlush(...). I suspect it is failed.
I have been migrating an existing application over to Spring Cloud's service discovery, Ribbon load balancing, and circuit breakers. The application already makes extensive use of the RestTemplate and I have been able to successfully use the load balanced version of the template. However, I have been testing the situation where there are two instances of a service and I drop one of those instances out of operation. I would like the RestTemplate to failover to the next server. From the research I have done, it appears that the fail-over logic exists in the Feign client and when using Zuul. It appears that the LoadBalancedRest template does not have logic for fail-over. In diving into the code, it looks like the RibbonClientHttpRequestFactory is using the netflix RestClient (which appears to have logic for doing retries).
So where do I go from here to get this working?
I would prefer to not use the Feign client because I would have to sweep A LOT of code.
I had found this link that suggested using the #Retryable annotation along with #HystrixCommand but this seems like something that should be a part of the load balanced rest template.
I did some digging into the code for RibbonClientHttpRequestFactory.RibbonHttpRequest:
protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException {
try {
addHeaders(headers);
if (outputStream != null) {
outputStream.close();
builder.entity(outputStream.toByteArray());
}
HttpRequest request = builder.build();
HttpResponse response = client.execute(request, config);
return new RibbonHttpResponse(response);
}
catch (Exception e) {
throw new IOException(e);
}
}
It appears that if I override this method and change it to use "client.executeWithLoadBalancer()" that I might be able to leverage the retry logic that is built into the RestClient? I guess I could create my own version of the RibbonClientHttpRequestFactory to do this?
Just looking for guidance on the best approach.
Thanks
To answer my own question:
Before I get into the details, a cautionary tale:
Eureka's self preservation mode sent me down a rabbit hole while testing the fail-over on my local machine. I recommend turning self preservation mode off while doing your testing. Because I was dropping nodes at a regular rate and then restarting (with a different instance ID using a random value), I tripped Eureka's self preservation mode. I ended up with many instances in Eureka that pointed to the same machine, same port. The fail-over was actually working but the next node that was chosen happened to be another dead instance. Very confusing at first!
I was able to get fail-over working with a modified version of RibbonClientHttpRequestFactory. Because RibbonAutoConfiguration creates a load balanced RestTemplate with this factory, rather then injecting this rest template, I create a new one with my modified version of the request factory:
protected RestTemplate restTemplate;
#Autowired
public void customizeRestTemplate(SpringClientFactory springClientFactory, LoadBalancerClient loadBalancerClient) {
restTemplate = new RestTemplate();
// Use a modified version of the http request factory that leverages the load balacing in netflix's RestClient.
RibbonRetryHttpRequestFactory lFactory = new RibbonRetryHttpRequestFactory(springClientFactory, loadBalancerClient);
restTemplate.setRequestFactory(lFactory);
}
The modified Request Factory is just a copy of RibbonClientHttpRequestFactory with two minor changes:
1) In createRequest, I removed the code that was selecting a server from the load balancer because the RestClient will do that for us.
2) In the inner class, RibbonHttpRequest, I changed executeInternal to call "executeWithLoadBalancer".
The full class:
#SuppressWarnings("deprecation")
public class RibbonRetryHttpRequestFactory implements ClientHttpRequestFactory {
private final SpringClientFactory clientFactory;
private LoadBalancerClient loadBalancer;
public RibbonRetryHttpRequestFactory(SpringClientFactory clientFactory, LoadBalancerClient loadBalancer) {
this.clientFactory = clientFactory;
this.loadBalancer = loadBalancer;
}
#Override
public ClientHttpRequest createRequest(URI originalUri, HttpMethod httpMethod) throws IOException {
String serviceId = originalUri.getHost();
IClientConfig clientConfig = clientFactory.getClientConfig(serviceId);
RestClient client = clientFactory.getClient(serviceId, RestClient.class);
HttpRequest.Verb verb = HttpRequest.Verb.valueOf(httpMethod.name());
return new RibbonHttpRequest(originalUri, verb, client, clientConfig);
}
public class RibbonHttpRequest extends AbstractClientHttpRequest {
private HttpRequest.Builder builder;
private URI uri;
private HttpRequest.Verb verb;
private RestClient client;
private IClientConfig config;
private ByteArrayOutputStream outputStream = null;
public RibbonHttpRequest(URI uri, HttpRequest.Verb verb, RestClient client, IClientConfig config) {
this.uri = uri;
this.verb = verb;
this.client = client;
this.config = config;
this.builder = HttpRequest.newBuilder().uri(uri).verb(verb);
}
#Override
public HttpMethod getMethod() {
return HttpMethod.valueOf(verb.name());
}
#Override
public URI getURI() {
return uri;
}
#Override
protected OutputStream getBodyInternal(HttpHeaders headers) throws IOException {
if (outputStream == null) {
outputStream = new ByteArrayOutputStream();
}
return outputStream;
}
#Override
protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException {
try {
addHeaders(headers);
if (outputStream != null) {
outputStream.close();
builder.entity(outputStream.toByteArray());
}
HttpRequest request = builder.build();
HttpResponse response = client.executeWithLoadBalancer(request, config);
return new RibbonHttpResponse(response);
}
catch (Exception e) {
throw new IOException(e);
}
//TODO: fix stats, now that execute is not called
// use execute here so stats are collected
/*
return loadBalancer.execute(this.config.getClientName(), new LoadBalancerRequest<ClientHttpResponse>() {
#Override
public ClientHttpResponse apply(ServiceInstance instance) throws Exception {}
});
*/
}
private void addHeaders(HttpHeaders headers) {
for (String name : headers.keySet()) {
// apache http RequestContent pukes if there is a body and
// the dynamic headers are already present
if (!isDynamic(name) || outputStream == null) {
List<String> values = headers.get(name);
for (String value : values) {
builder.header(name, value);
}
}
}
}
private boolean isDynamic(String name) {
return name.equals("Content-Length") || name.equals("Transfer-Encoding");
}
}
public class RibbonHttpResponse extends AbstractClientHttpResponse {
private HttpResponse response;
private HttpHeaders httpHeaders;
public RibbonHttpResponse(HttpResponse response) {
this.response = response;
this.httpHeaders = new HttpHeaders();
List<Map.Entry<String, String>> headers = response.getHttpHeaders().getAllHeaders();
for (Map.Entry<String, String> header : headers) {
this.httpHeaders.add(header.getKey(), header.getValue());
}
}
#Override
public InputStream getBody() throws IOException {
return response.getInputStream();
}
#Override
public HttpHeaders getHeaders() {
return this.httpHeaders;
}
#Override
public int getRawStatusCode() throws IOException {
return response.getStatus();
}
#Override
public String getStatusText() throws IOException {
return HttpStatus.valueOf(response.getStatus()).name();
}
#Override
public void close() {
response.close();
}
}
}
I had the same problem but then, out of the box, everything was working (using a #LoadBalanced RestTemplate). I am using Finchley version of Spring Cloud, and I think my problem was that I was not explicity adding spring-retry in my pom configuration. I'll leave here my spring-retry related yml configuration (remember this only works with #LoadBalanced RestTemplate, Zuul of Feign):
spring:
# Ribbon retries on
cloud:
loadbalancer:
retry:
enabled: true
# Ribbon service config
my-service:
ribbon:
MaxAutoRetries: 3
MaxAutoRetriesNextServer: 1
OkToRetryOnAllOperations: true
retryableStatusCodes: 500, 502
I've an application deployed in JBoss with multiple MDBs deployed using JBoss JMS implementation, each one with a different configuration of MDB Pool Size. I was looking forward to some kind of mechanism where we can have a listener on each MDB Pool size where we can check if at any point all instances from the MDB pool are getting utilized. This will help in analyzing and configuring the appropriate MDB pool size for each MDB.
We use Jamon to monitor instances of MDBs, like this:
#MessageDriven
#TransactionManagement(value = TransactionManagementType.CONTAINER)
#TransactionAttribute(value = TransactionAttributeType.REQUIRED)
#ResourceAdapter("wmq.jmsra.rar")
#AspectDomain("YourDomainName")
public class YourMessageDrivenBean implements MessageListener
{
// jamon package constant
protected static final String WB_ONMESSAGE = "wb.onMessage";
// instance counter
private static AtomicInteger counter = new AtomicInteger(0);
private int instanceIdentifier = 0;
#Resource
MessageDrivenContext ctx;
#Override
public void onMessage(Message message)
{
final Monitor monall = MonitorFactory.start(WB_ONMESSAGE);
final Monitor mon = MonitorFactory.start(WB_ONMESSAGE + "." + toString()
+ "; mdb instance identifier=" + instanceIdentifier);
try {
// process your message here
}
} catch (final Exception x) {
log.error("Error onMessage " + x.getMessage(), x);
ctx.setRollbackOnly();
} finally {
monall.stop();
mon.stop();
}
}
#PostConstruct
public void init()
{
instanceIdentifier = counter.incrementAndGet();
log.debug("constructed instance #" + instanceIdentifier);
}
}
You can then see in the Jamon-Monitor every created instance of your MDB.