/*
* Copyright 2015 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.
*
* 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.kie.server.jms;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import org.kie.server.api.ConversationId;
import org.kie.server.api.KieServerConstants;
import org.kie.server.api.KieServerEnvironment;
import org.kie.server.api.commands.CommandScript;
import org.kie.server.api.marshalling.Marshaller;
import org.kie.server.api.marshalling.MarshallerFactory;
import org.kie.server.api.marshalling.MarshallingFormat;
import org.kie.server.api.model.ReleaseId;
import org.kie.server.api.model.ServiceResponsesList;
import org.kie.server.services.api.KieContainerCommandService;
import org.kie.server.services.api.KieContainerInstance;
import org.kie.server.services.api.KieServerExtension;
import org.kie.server.services.impl.KieServerImpl;
import org.kie.server.services.impl.KieServerLocator;
import org.kie.server.services.impl.security.adapters.JMSSecurityAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.kie.server.api.jms.JMSConstants.*;
@TransactionManagement(TransactionManagementType.BEAN)
@MessageDriven(name = "KieServerMDB", activationConfig = {
@ActivationConfigProperty(propertyName = "destinationJndiName", propertyValue = "queue/KIE.SERVER.REQUEST"),
@ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/KIE.SERVER.REQUEST"),
@ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
@ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge")})
public class KieServerMDB
implements MessageListener {
private static final Logger logger = LoggerFactory.getLogger( KieServerMDB.class );
// Constants / properties
private String RESPONSE_QUEUE_NAME = null;
private static final String DEFAULT_RESPONSE_QUEUE_NAME = "queue/KIE.SERVER.RESPONSE";
private static final String ID_NECESSARY = "This id is needed to be able to match a request to a response message.";
@Resource(mappedName = "java:/JmsXA")
private ConnectionFactory factory;
// Initialized in @PostConstruct
private Session session;
private Connection connection;
// @Resource(lookup = "java:app/RetryTrackerSingleton")
// private RetryTrackerSingleton retryTracker;
private KieServerImpl kieServer;
private Map<MarshallingFormat, Marshaller> marshallers;
@PostConstruct
public void init() {
RESPONSE_QUEUE_NAME = System.getProperty( KieServerConstants.CFG_KIE_SERVER_RESPONSE_QUEUE, DEFAULT_RESPONSE_QUEUE_NAME );
boolean sessionTransacted = Boolean.parseBoolean(System.getProperty(KieServerConstants.CFG_KIE_SERVER_JMS_SESSION_TX, "false"));
int sessionAck = Integer.parseInt(System.getProperty(KieServerConstants.CFG_KIE_SERVER_JMS_SESSION_ACK, String.valueOf(Session.AUTO_ACKNOWLEDGE)));
try {
connection = factory.createConnection();
session = connection.createSession( sessionTransacted, sessionAck );
connection.start();
} catch ( JMSException jmse ) {
// Unable to create connection/session, so no need to try send the message (4.) either
String errMsg = "Unable to open new session to send response messages";
logger.error( errMsg, jmse );
throw new JMSRuntimeException( errMsg, jmse );
}
kieServer = KieServerLocator.getInstance();
marshallers = new ConcurrentHashMap<MarshallingFormat, Marshaller>( );
// the commands classloader -- not sure if there is a better way to get a reference to it
ClassLoader classLoader = CommandScript.class.getClassLoader();
marshallers.put( MarshallingFormat.XSTREAM, MarshallerFactory.getMarshaller( MarshallingFormat.XSTREAM, classLoader ) );
marshallers.put( MarshallingFormat.JAXB, MarshallerFactory.getMarshaller( MarshallingFormat.JAXB, classLoader ) );
marshallers.put( MarshallingFormat.JSON, MarshallerFactory.getMarshaller( MarshallingFormat.JSON, classLoader ) );
}
@PreDestroy
public void cleanup() {
try {
if ( connection != null ) {
connection.close();
connection = null;
}
if ( session != null ) {
session.close();
session = null;
}
} catch ( JMSException jmse ) {
String errMsg = "Unable to close " + (connection == null ? "session" : "connection");
logger.error( errMsg, jmse );
throw new JMSRuntimeException( errMsg, jmse );
}
}
public void onMessage(Message message) {
try {
String username = null;
String password = null;
try {
username = message.getStringProperty(USER_PROPERTY_NAME);
password = message.getStringProperty(PASSWRD_PROPERTY_NAME);
} catch (JMSException jmse) {
}
if (username != null && password != null) {
JMSSecurityAdapter.login(username, password);
}
KieContainerCommandService executor = null;
// 0. Get msg correlation id (for response)
String msgCorrId = null;
try {
msgCorrId = message.getJMSCorrelationID();
} catch (JMSException jmse) {
String errMsg = "Unable to retrieve JMS correlation id from message! " + ID_NECESSARY;
throw new JMSRuntimeException(errMsg, jmse);
}
String targetCapability = getStringProperty(message, TARGET_CAPABILITY_PROPERTY_NAME, "KieServer"); // for backward compatibility default to KieServer
String containerId = getStringProperty(message, CONTAINER_ID_PROPERTY_NAME, null);
String conversationId = getStringProperty(message, CONVERSATION_ID_PROPERTY_NAME, null);
int interactionPattern = getIntProperty(message, INTERACTION_PATTERN_PROPERTY_NAME, REQUEST_REPLY_PATTERN);
// 1. get marshalling info
MarshallingFormat format = null;
String classType = null;
try {
classType = message.getStringProperty(CLASS_TYPE_PROPERTY_NAME);
if (!message.propertyExists(SERIALIZATION_FORMAT_PROPERTY_NAME)) {
format = MarshallingFormat.JAXB;
} else {
int intFormat = message.getIntProperty(SERIALIZATION_FORMAT_PROPERTY_NAME);
logger.debug("Serialization format (int) is " + intFormat);
format = MarshallingFormat.fromId(intFormat);
logger.debug("Serialization format is " + format);
if (format == null) {
String errMsg = "Unsupported marshalling format '" + intFormat + "' from message " + msgCorrId + ".";
throw new JMSRuntimeException(errMsg);
}
}
} catch (JMSException jmse) {
String errMsg = "Unable to retrieve property '" + SERIALIZATION_FORMAT_PROPERTY_NAME + "' from message " + msgCorrId + ".";
throw new JMSRuntimeException(errMsg, jmse);
}
// 2. get marshaller
Marshaller marshaller = getMarshaller(containerId, format);
logger.debug("Selected marshaller is " + marshaller);
// 3. deserialize request
CommandScript script = unmarshallRequest(message, msgCorrId, marshaller, format);
logger.debug("Target capability is {}", targetCapability);
for (KieServerExtension extension : kieServer.getServerExtensions()) {
KieContainerCommandService tmp = extension.getAppComponents(KieContainerCommandService.class);
if (tmp != null && extension.getImplementedCapability().equalsIgnoreCase(targetCapability)) {
executor = tmp;
logger.debug("Extension {} returned command executor {} with capability {}", extension, executor, extension.getImplementedCapability());
break;
}
}
if (executor == null) {
throw new IllegalStateException("No executor found for script execution");
}
// 4. process request
ServiceResponsesList response = executor.executeScript(script, format, classType);
if (interactionPattern < UPPER_LIMIT_REPLY_INTERACTION_PATTERNS) {
logger.debug("Response message is about to be sent according to selected interaction pattern {}", interactionPattern);
// 5. serialize response
Message msg = marshallResponse(session, msgCorrId, format, marshaller, response);
// set conversation id for routing
if (containerId != null && (conversationId == null || conversationId.trim().isEmpty())) {
try {
KieContainerInstance containerInstance = kieServer.getServerRegistry().getContainer(containerId);
if (containerInstance != null) {
ReleaseId releaseId = containerInstance.getResource().getResolvedReleaseId();
if (releaseId == null) {
releaseId = containerInstance.getResource().getReleaseId();
}
conversationId = ConversationId.from(KieServerEnvironment.getServerId(), containerId, releaseId).toString();
}
} catch (Exception e) {
logger.warn("Unable to build conversation id due to {}", e.getMessage(), e);
}
}
try {
if (conversationId != null) {
msg.setStringProperty(CONVERSATION_ID_PROPERTY_NAME, conversationId);
}
} catch (JMSException e) {
logger.debug("Unable to set conversation id on response message due to {}", e.getMessage());
}
// 6. send response
sendResponse(msgCorrId, format, msg);
} else {
logger.debug("Response message is skipped according to selected interaction pattern {}", FIRE_AND_FORGET_PATTERN);
}
} finally {
JMSSecurityAdapter.logout();
}
}
private static CommandScript unmarshallRequest(Message message, String msgId, Marshaller serializationProvider, MarshallingFormat format) {
CommandScript cmdMsg = null;
try {
String msgStrContent = ((TextMessage) message).getText();
logger.debug("About to unmarshal content '{}'", msgStrContent);
cmdMsg = serializationProvider.unmarshall( msgStrContent, CommandScript.class );
} catch (JMSException jmse) {
String errMsg = "Unable to read information from message " + msgId + ".";
throw new JMSRuntimeException(errMsg, jmse);
} catch (Exception e) {
String errMsg = "Unable to unmarshall request to " + CommandScript.class.getSimpleName() + " [msg id: " + msgId + "].";
throw new JMSRuntimeException(errMsg, e);
}
return cmdMsg;
}
private static Message marshallResponse(Session session, String msgId, MarshallingFormat format, Marshaller marshaller, ServiceResponsesList response ) {
TextMessage textMsg = null;
try {
String msgStr = marshaller.marshall( response );
textMsg = session.createTextMessage(msgStr);
textMsg.setIntProperty( SERIALIZATION_FORMAT_PROPERTY_NAME, format.getId());
} catch (JMSException jmse) {
String errMsg = "Unable to create response message or write to it [msg id: " + msgId + "].";
throw new JMSRuntimeException(errMsg, jmse);
} catch (Exception e) {
String errMsg = "Unable to serialize " + response.getClass().getSimpleName() + " to a String.";
throw new JMSRuntimeException(errMsg, e);
}
return textMsg;
}
private void sendResponse(String msgCorrId, MarshallingFormat format, Message msg) {
// set correlation id in response message
try {
msg.setJMSCorrelationID(msgCorrId);
} catch (JMSException jmse) {
// Without correlation id, receiver won't know what the response relates to
String errMsg = "Unable to set correlation id of response to msg id " + msgCorrId;
logger.error(errMsg, jmse);
return;
}
// send response message
MessageProducer producer = null;
try {
Queue responseQueue = (Queue) (new InitialContext()).lookup(RESPONSE_QUEUE_NAME);
producer = session.createProducer(responseQueue);
producer.send(msg);
} catch (NamingException ne) {
String errMsg = "Unable to lookup response queue " + RESPONSE_QUEUE_NAME + " to send msg " + msgCorrId
+ " (Is " + KieServerConstants.CFG_KIE_SERVER_RESPONSE_QUEUE + " incorrect?).";
logger.error(errMsg, ne);
} catch (JMSException jmse) {
String errMsg = "Unable to send msg " + msgCorrId + " to " + RESPONSE_QUEUE_NAME;
logger.error(errMsg, jmse);
} finally {
if( producer != null ) {
try {
producer.close();
} catch( JMSException e ) {
logger.debug("Closing the producer resulted in an exception: " + e.getMessage(), e);
}
}
}
}
protected Marshaller getMarshaller(String containerId, MarshallingFormat format) {
if (containerId == null || containerId.isEmpty()) {
return marshallers.get(format);
}
KieContainerInstance kieContainerInstance = kieServer.getServerRegistry().getContainer(containerId);
if (kieContainerInstance != null && kieContainerInstance.getKieContainer() != null) {
return kieContainerInstance.getMarshaller(format);
}
return marshallers.get(format);
}
protected String getStringProperty(Message message, String name, String defaultValue) {
try {
if (message.propertyExists(name)) {
return message.getStringProperty(name);
}
} catch (JMSException jmse) {
String errMsg = "Unable to retrieve property '" + name + "' from message " + message + ".";
logger.debug(errMsg, jmse);
}
return defaultValue;
}
protected int getIntProperty(Message message, String name, int defaultValue) {
try {
if (message.propertyExists(name)) {
return message.getIntProperty(name);
}
} catch (JMSException jmse) {
String errMsg = "Unable to retrieve property '" + name + "' from message " + message + ".";
logger.debug(errMsg, jmse);
}
return defaultValue;
}
}