
org.perfcake.message.sender.RequestResponseJms11Sender Maven / Gradle / Ivy
/*
* -----------------------------------------------------------------------\
* PerfCake
*
* Copyright (C) 2010 - 2016 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.perfcake.message.sender;
import org.perfcake.PerfCakeException;
import org.perfcake.reporting.MeasurementUnit;
import org.perfcake.util.StringTemplate;
import org.perfcake.util.Utils;
import org.perfcake.util.properties.MandatoryProperty;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.Serializable;
import java.util.Properties;
import java.util.UUID;
import javax.jms.BytesMessage;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.ObjectMessage;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
/**
* Sends messages and receives responses over JMS.
*
* @author Pavel Macík
* @author Martin Večeřa
*/
public class RequestResponseJms11Sender extends Jms11Sender {
/**
* Logger.
*/
private static final Logger log = LogManager.getLogger(RequestResponseJms11Sender.class);
/**
* JMS initial context for response reception.
*/
protected InitialContext responseCtx = null;
/**
* JMS destination connection factory for response reception.
*/
protected ConnectionFactory rcf = null;
/**
* JMS connection to the response destination.
*/
private Connection responseConnection;
/**
* JMS session to the response destination.
*/
private Session responseSession;
/**
* JMS consumer for the response destination.
*/
private MessageConsumer responseReceiver;
/**
* Where to read the responses from.
*/
@MandatoryProperty
private StringTemplate responseTarget = null;
/**
* Timeout for receiving the response in ms for a single attempt.
*/
private long receivingTimeout = 1000; // default 1s
/**
* Maximal number of attempts to read the response.
*/
private int receiveAttempts = 5;
/**
* Correlation ID of this sender instance for it to read only the messages that it has sent.
*/
private final String correlationId = UUID.randomUUID().toString();
/**
* Should the correlation ID be used in the JMS communication? Turning it off (false) allows the sender to read any response from the response destination.
*/
private boolean useCorrelationId = false;
/**
* Indicates whether the JMS message is auto-acknowledged by the receiver (true) or by client (false).
*/
protected boolean autoAck = true;
/**
* JMS connection factory property for the response reception.
*/
protected String responseConnectionFactory = null;
/**
* JNDI context factory property for the response reception.
*/
protected String responseJndiContextFactory = null;
/**
* JNDI URL property for the response reception.
*/
protected String responseJndiUrl = null;
/**
* JNDI username property for the response reception.
*/
protected String responseJndiSecurityPrincipal = null;
/**
* JNDI password for the response reception.
*/
protected String responseJndiSecurityCredentials = null;
/**
* JMS username used for the response reception.
*/
protected String responseUsername = null;
/**
* JMS password used for the response reception.
*/
protected String responsePassword = null;
@Override
public void doInit(final Properties messageAttributes) throws PerfCakeException {
super.doInit(messageAttributes);
try {
if (responseTarget == null || responseTarget.toString().equals("")) {
throw new PerfCakeException("responseTarget property is not defined in the scenario or is empty");
} else {
initResponseConnection();
final String safeResponseTarget = messageAttributes == null ? responseTarget.toString() : responseTarget.toString(messageAttributes);
final Destination responseDestination = (Destination) responseCtx.lookup(safeResponseTarget);
if (transacted && !autoAck) {
log.warn("AutoAck setting is ignored with a transacted session. Creating a transacted session.");
}
responseSession = responseConnection.createSession(transacted, autoAck ? Session.AUTO_ACKNOWLEDGE : Session.CLIENT_ACKNOWLEDGE);
if (useCorrelationId) {
responseReceiver = responseSession.createConsumer(responseDestination, "JMSCorrelationID='" + correlationId + "'");
} else {
responseReceiver = responseSession.createConsumer(responseDestination);
}
}
} catch (PerfCakeException pce) {
throw pce;
} catch (Exception e) {
throw new PerfCakeException("Cannot initialize response receiver: ", e);
}
}
private void initResponseConnection() throws Exception {
try {
final Properties ctxProps = new Properties();
Utils.setFirstNotNullProperty(ctxProps, Context.PROVIDER_URL, responseJndiUrl, jndiUrl);
Utils.setFirstNotNullProperty(ctxProps, Context.INITIAL_CONTEXT_FACTORY, responseJndiContextFactory, jndiContextFactory);
Utils.setFirstNotNullProperty(ctxProps, Context.SECURITY_PRINCIPAL, responseJndiSecurityPrincipal); // we want to be able not to specify security principal even when it is used for sending the messages
Utils.setFirstNotNullProperty(ctxProps, Context.SECURITY_CREDENTIALS, responseJndiSecurityCredentials);
if (ctxProps.isEmpty()) {
responseCtx = new InitialContext();
} else {
responseCtx = new InitialContext(ctxProps);
}
rcf = (ConnectionFactory) responseCtx.lookup(Utils.getFirstNotNull(responseConnectionFactory, connectionFactory));
if (checkCredentials(responseUsername, responsePassword)) {
responseConnection = rcf.createConnection(responseUsername, responsePassword);
} else {
responseConnection = rcf.createConnection();
}
responseConnection.start();
} catch (JMSException | NamingException | RuntimeException e) {
throw new PerfCakeException(e);
}
}
@Override
public void doClose() throws PerfCakeException {
try {
try {
super.doClose();
} finally {
try {
if (responseReceiver != null) {
responseReceiver.close();
}
} finally {
try {
if (transacted) {
responseSession.commit();
}
} finally {
try {
if (responseSession != null) {
responseSession.close();
}
} finally {
if (responseConnection != null) {
responseConnection.close();
}
}
}
}
}
} catch (final JMSException e) {
throw new PerfCakeException(e);
}
}
@Override
public void preSend(final org.perfcake.message.Message message, final Properties messageAttributes) throws Exception {
super.preSend(message, messageAttributes);
if (useCorrelationId) { // set the correlation ID
mess.setJMSCorrelationID(correlationId);
}
}
@Override
public Serializable doSend(final org.perfcake.message.Message message, final MeasurementUnit measurementUnit) throws Exception {
// send the request message
super.doSend(message, measurementUnit);
try {
if (transacted) {
session.commit();
}
// receive response
Serializable retVal = null;
int attempts = 0;
do {
attempts++;
final Message response = responseReceiver.receive(receivingTimeout);
if (response == null) {
if (log.isDebugEnabled()) {
log.debug("No message in " + responseTarget + " received within the specified timeout (" + receivingTimeout + " ms). Retrying (" + attempts + "/" + receiveAttempts + ") ...");
}
} else {
if (!autoAck) {
response.acknowledge();
}
if (response instanceof ObjectMessage) {
retVal = ((ObjectMessage) response).getObject();
} else if (response instanceof TextMessage) {
retVal = ((TextMessage) response).getText();
} else if (response instanceof BytesMessage) {
final byte[] bytes = new byte[(int) (((BytesMessage) response).getBodyLength())];
((BytesMessage) response).readBytes(bytes);
retVal = bytes;
} else {
throw new PerfCakeException("Received message is not one of (ObjectMessage, TextMessage, BytesMessage) but: " + response.getClass().getName());
}
if (transacted) {
responseSession.commit();
}
}
} while (retVal == null && attempts < receiveAttempts); // @checkstyle.ignore(RightCurly) - Do-while cycle should have while after the brace.
if (retVal == null) {
throw new PerfCakeException("No message in " + responseTarget + " received within the specified timeout (" + receivingTimeout + " ms) in " + receiveAttempts + " attempt(s).");
}
return retVal;
} catch (final JMSException e) {
throw new PerfCakeException(e);
}
}
/**
* Sets the configuration of using the correlation ID in response retrieval.
*
* @param useCorrelationId
* When true, only the messages that are response to the original message can be read from the response destination. Otherwise, any response message can be read.
* @return Instance of this for fluent API.
*/
public RequestResponseJms11Sender setUseCorrelationId(final boolean useCorrelationId) {
this.useCorrelationId = useCorrelationId;
return this;
}
/**
* Gets the configuration of using the correlation ID.
*
* @return Whether the sender receives only the response messages with the appropriate correlation ID, i. e. responses to messages sent by this sender instance.
*/
public boolean isUseCorrelationId() {
return useCorrelationId;
}
/**
* Gets the number of milliseconds to wait for the response message.
*
* @return Number of milliseconds to wait for the response message.
*/
public long getReceivingTimeout() {
return receivingTimeout;
}
/**
* Sets the number of milliseconds to wait for the response message.
*
* @param receivingTimeout
* Number of milliseconds to wait for the response message.
* @return Instance of this for fluent API.
*/
public RequestResponseJms11Sender setReceivingTimeout(final long receivingTimeout) {
this.receivingTimeout = receivingTimeout;
return this;
}
/**
* Gets the maximum number of attempts to read the response message.
*
* @return The maximum number of attempts to read the response message.
*/
public int getReceiveAttempts() {
return receiveAttempts;
}
/**
* Sets the maximum number of attempts to read the response message.
*
* @param receiveAttempts
* The maximum number of attempts to read the response message.
* @return Instance of this for fluent API.
*/
public RequestResponseJms11Sender setReceiveAttempts(final int receiveAttempts) {
this.receiveAttempts = receiveAttempts;
return this;
}
/**
* Gets the destination where the response message is being read from.
*
* @return The name of the response destination.
*/
public String getResponseTarget() {
return responseTarget.toString();
}
/**
* Sets the name of the destination where the response messages should be read from.
*
* @param responseTarget
* The name of the response destination.
* @return Instance of this for fluent API.
*/
public RequestResponseJms11Sender setResponseTarget(final String responseTarget) {
this.responseTarget = new StringTemplate(responseTarget);
return this;
}
/**
* Is JMS message delivery automatically acknowledged?
*
* @return The automatic acknowledgement mode.
*/
public boolean isAutoAck() {
return autoAck;
}
/**
* Enables/disables the automatic acknowledgement of JMS message delivery.
*
* @param autoAck
* true
to enable the automatic acknowledgement.
* @return Instance of this for fluent API.
*/
public RequestResponseJms11Sender setAutoAck(final boolean autoAck) {
this.autoAck = autoAck;
return this;
}
/**
* Gets the connection factory used for the response reception.
*
* @return The connection factory used for the response reception.
*/
public String getResponseConnectionFactory() {
return responseConnectionFactory;
}
/**
* Sets the connection factory used for the response reception.
* When this is not set, the connection factory used to send the messages is used.
*
* @param responseConnectionFactory
* The connection factory used for the response reception.
* @return Instance of this for fluent API.
*/
public RequestResponseJms11Sender setResponseConnectionFactory(final String responseConnectionFactory) {
this.responseConnectionFactory = responseConnectionFactory;
return this;
}
/**
* Gets the JNDI context factory used for the response reception.
*
* @return JNDI context factory used for the response reception.
*/
public String getResponseJndiContextFactory() {
return responseJndiContextFactory;
}
/**
* Sets the JNDI context factory used for the response reception.
*
* @param responseJndiContextFactory
* The JNDI context factory used for the response reception.
* @return Instance of this for fluent API.
*/
public RequestResponseJms11Sender setResponseJndiContextFactory(final String responseJndiContextFactory) {
this.responseJndiContextFactory = responseJndiContextFactory;
return this;
}
/**
* Gets the JNDI URL used for the response reception.
*
* @return The JNDI URL used for the response reception.
*/
public String getResponseJndiUrl() {
return responseJndiUrl;
}
/**
* Sets the JNDI URL used for the response reception.
* When this is not set, the JNDI properties set for sending messages are taken.
*
* @param responseJndiUrl
* The JNDI URL used for the response reception.
* @return Instance of this for fluent API.
*/
public RequestResponseJms11Sender setResponseJndiUrl(final String responseJndiUrl) {
this.responseJndiUrl = responseJndiUrl;
return this;
}
/**
* Gets the JNDI security principal used for the response reception.
*
* @return The security principal used for the response reception.
*/
public String getResponseJndiSecurityPrincipal() {
return responseJndiSecurityPrincipal;
}
/**
* Sets the JNDI security principal used for the response reception.
* When this is not set, an unsecured connection is used.
*
* @param responseJndiSecurityPrincipal
* The security principal used for the response reception.
* @return Instance of this for fluent API.
*/
public RequestResponseJms11Sender setResponseJndiSecurityPrincipal(final String responseJndiSecurityPrincipal) {
this.responseJndiSecurityPrincipal = responseJndiSecurityPrincipal;
return this;
}
/**
* Gets the JNDI security credentials used for the response reception.
*
* @return The JNDI security credentials used for the response reception.
*/
public String getResponseJndiSecurityCredentials() {
return responseJndiSecurityCredentials;
}
/**
* Sets the value of the JNDI security credentials for the response reception.
* When this is not set and JNDI security principal for the response reception was set, empty password is used.
* Otherwise, an unsecured connection is used.
*
* @param responseJndiSecurityCredentials
* The JNDI security credentials to be used for the response reception.
* @return Instance of this for fluent API.
*/
public RequestResponseJms11Sender setResponseJndiSecurityCredentials(final String responseJndiSecurityCredentials) {
this.responseJndiSecurityCredentials = responseJndiSecurityCredentials;
return this;
}
/**
* Gets the JMS username used for response reception.
*
* @return The JMS username used for response reception.
*/
public String getResponseUsername() {
return responseUsername;
}
/**
* Sets the JMS username used for response reception.
*
* @param responseUsername
* The JMS username used for response reception.
* @return Instance of this for fluent API.
*/
public RequestResponseJms11Sender setResponseUsername(final String responseUsername) {
this.responseUsername = responseUsername;
return this;
}
/**
* Gets the JMS password used for response reception.
*
* @return The JMS password used for response reception.
*/
public String getResponsePassword() {
return responsePassword;
}
/**
* Sets the JMS password used for response reception.
*
* @param responsePassword
* The JMS username used for response reception.
* @return Instance of this for fluent API.
*/
public RequestResponseJms11Sender setResponsePassword(final String responsePassword) {
this.responsePassword = responsePassword;
return this;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy