
org.kie.remote.services.jms.RequestMessageBean Maven / Gradle / Ivy
The newest version!
package org.kie.remote.services.jms;
import static org.kie.services.client.serialization.SerializationConstants.*;
import static org.kie.services.client.serialization.jaxb.rest.JaxbRequestStatus.FORBIDDEN;
import java.security.Principal;
import java.security.acl.Group;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.inject.Inject;
import javax.jms.BytesMessage;
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.naming.InitialContext;
import javax.naming.NamingException;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import org.jbpm.services.task.commands.TaskCommand;
import org.jbpm.services.task.identity.JAASUserGroupCallbackImpl;
import org.jbpm.services.task.identity.adapter.UserGroupAdapter;
import org.kie.api.command.Command;
import org.kie.remote.services.cdi.DeploymentInfoBean;
import org.kie.remote.services.cdi.ProcessRequestBean;
import org.kie.remote.services.exception.KieRemoteServicesInternalError;
import org.kie.remote.services.exception.KieRemoteServicesRuntimeException;
import org.kie.remote.services.jms.request.BackupIdentityProviderProducer;
import org.kie.remote.services.jms.security.JmsUserGroupAdapter;
import org.kie.remote.services.jms.security.UserPassCallbackHandler;
import org.kie.services.client.serialization.JaxbSerializationProvider;
import org.kie.services.client.serialization.SerializationException;
import org.kie.services.client.serialization.SerializationProvider;
import org.kie.services.client.serialization.jaxb.impl.JaxbCommandsRequest;
import org.kie.services.client.serialization.jaxb.impl.JaxbCommandsResponse;
import org.kie.services.shared.AcceptedCommands;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* There are thus multiple queues to which an instance of this class could listen to, which is why
* the (JMS queue) configuration is done in the ejb-jar.xml file.
*
* Doing the configuration in the ejb-jar.xml file which allows us to configure instances of one class
* to listen to more than one queue.
*
* Also: responses to requests are not placed on a reply-to queue, but on the specified answer queue.
*/
public class RequestMessageBean implements MessageListener {
private static final Logger logger = LoggerFactory.getLogger(RequestMessageBean.class);
// JMS resources
@Resource(mappedName = "java:/JmsXA")
private ConnectionFactory factory;
// Initialized in @PostConstruct
private Session session;
private Connection connection;
@Inject
private RetryTrackerSingleton retryTracker;
// KIE resources
@Inject
protected DeploymentInfoBean runtimeMgrMgr;
@Inject
protected ProcessRequestBean processRequestBean;
@Inject
private BackupIdentityProviderProducer backupIdentityProviderProducer;
// Constants / properties
private String RESPONSE_QUEUE_NAME = null;
private static String RESPONSE_QUEUE_NAME_PROPERTY = "kie.services.jms.queues.response";
private static final String ID_NECESSARY = "This id is needed to be able to match a request to a response message.";
@PostConstruct
public void init() {
RESPONSE_QUEUE_NAME = System.getProperty(RESPONSE_QUEUE_NAME_PROPERTY, "queue/KIE.RESPONSE.ALL");
try {
connection = factory.createConnection();
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
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 KieRemoteServicesRuntimeException(errMsg, jmse);
}
}
@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 KieRemoteServicesRuntimeException(errMsg, jmse);
}
}
// See EJB 3.1 fr, 5.4.12 and 13.3.3: BMT for which the (last) ut.commit() confirms message reception
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void onMessage(Message message) {
String msgId = null;
boolean redelivered = false;
try {
msgId = message.getJMSMessageID();
redelivered = message.getJMSRedelivered();
} catch (JMSException jmse) {
String errMsg = "Unable to retrieve JMS " + (msgId == null ? "redelivered flag" : "message id")
+ " from JMS message. Message will not be returned to queue.";
logger.warn(errMsg, jmse);
}
if( redelivered ) {
if (retryTracker.maxRetriesReached(msgId)) {
logger.warn("Maximum number of retries (" + retryTracker.getMaximumLimitRetries() + ") reached for message " + msgId );
logger.warn("Acknowledging message but NOT processing it.");
return;
} else {
logger.warn("Retry number " + retryTracker.incrementRetries(msgId) + " of message " + msgId);
}
}
// 0. Get msg correlation id (for response)
String msgCorrId = null;
JaxbCommandsResponse jaxbResponse = null;
try {
msgCorrId = message.getJMSCorrelationID();
} catch (JMSException jmse) {
String errMsg = "Unable to retrieve JMS correlation id from message! " + ID_NECESSARY;
throw new KieRemoteServicesRuntimeException(errMsg, jmse);
}
// 0. get serialization info
int serializationType = -1;
try {
if (!message.propertyExists(SERIALIZATION_TYPE_PROPERTY_NAME)) {
// default is JAXB
serializationType = JaxbSerializationProvider.JMS_SERIALIZATION_TYPE;
} else {
serializationType = message.getIntProperty(SERIALIZATION_TYPE_PROPERTY_NAME);
}
} catch (JMSException jmse) {
String errMsg = "Unable to get properties from message " + msgCorrId + ".";
throw new KieRemoteServicesRuntimeException(errMsg, jmse);
}
SerializationProvider serializationProvider;
switch (serializationType) {
case JaxbSerializationProvider.JMS_SERIALIZATION_TYPE:
serializationProvider = getJaxbSerializationProvider(message);
break;
default:
throw new KieRemoteServicesInternalError("Unknown serialization type: " + serializationType);
}
// 1. deserialize request
JaxbCommandsRequest cmdsRequest = deserializeRequest(message, msgCorrId, serializationProvider, serializationType);
// 2. security/identity
backupIdentityProviderProducer.createBackupIdentityProvider(cmdsRequest.getUser());
cmdsRequest.setUserPass(getUserPass(message));
// 3. process request
jaxbResponse = jmsProcessJaxbCommandsRequest(cmdsRequest);
// 4. serialize response
Message msg = serializeResponse(session, msgCorrId, serializationType, serializationProvider, jaxbResponse);
// 5. send response
sendResponse(msgCorrId, serializationType, msg);
if (redelivered) {
retryTracker.clearRetries(msgId);
}
}
private void sendResponse(String msgCorrId, int serializationType, Message msg) {
// 3b. set correlation id in response messgae
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;
}
// 3c. send response message
try {
Queue responseQueue = (Queue) (new InitialContext()).lookup(RESPONSE_QUEUE_NAME);
MessageProducer 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 " + RESPONSE_QUEUE_NAME_PROPERTY + " incorrect?).";
logger.error(errMsg, ne);
} catch (JMSException jmse) {
String errMsg = "Unable to send msg " + msgCorrId + " to " + RESPONSE_QUEUE_NAME;
logger.error(errMsg, jmse);
}
}
// De/Serialization helper methods -------------------------------------------------------------------------------------------
private static JaxbCommandsRequest deserializeRequest(Message message, String msgId, SerializationProvider serializationProvider, int serializationType) {
JaxbCommandsRequest cmdMsg = null;
try {
String msgStrContent = null;
switch (serializationType) {
case JaxbSerializationProvider.JMS_SERIALIZATION_TYPE:
msgStrContent = ((BytesMessage) message).readUTF();
cmdMsg = (JaxbCommandsRequest) serializationProvider.deserialize(msgStrContent);
break;
default:
throw new KieRemoteServicesRuntimeException("Unknown serialization type when deserializing message " + msgId + ":" + serializationType);
}
} catch (JMSException jmse) {
String errMsg = "Unable to read information from message " + msgId + ".";
throw new KieRemoteServicesRuntimeException(errMsg, jmse);
} catch (Exception e) {
String errMsg = "Unable to serialize String to " + JaxbCommandsRequest.class.getSimpleName() + " [msg id: " + msgId + "].";
throw new KieRemoteServicesInternalError(errMsg, e);
}
return cmdMsg;
}
private SerializationProvider getJaxbSerializationProvider(Message message) {
SerializationProvider serializationProvider;
Set> serializationClasses = new HashSet>();
try {
String deploymentId = null;
ClassLoader classLoader = null;
// Add classes from deployment (and get deployment classloader)
if (message.propertyExists(DEPLOYMENT_ID_PROPERTY_NAME)) {
deploymentId = message.getStringProperty(DEPLOYMENT_ID_PROPERTY_NAME);
Collection> deploymentClasses = runtimeMgrMgr.getDeploymentClasses(deploymentId);
if (!deploymentClasses.isEmpty()) {
logger.debug("Added classes from {} to serialization context.", deploymentId);
serializationClasses.addAll(deploymentClasses);
// KieContainer (deployment) classloader
classLoader = deploymentClasses.iterator().next().getClassLoader();
} else {
logger.warn("Deployment id '{}' was included in message but no classes were retrieved from deployment!", deploymentId);
}
}
if (classLoader == null) {
// Application classloader
classLoader = this.getClass().getClassLoader();
}
// Add other classes that might only have been added to the war/application
if (message.propertyExists(EXTRA_JAXB_CLASSES_PROPERTY_NAME)) {
String extraClassesString = message.getStringProperty(EXTRA_JAXB_CLASSES_PROPERTY_NAME);
Set> moreExtraClasses = JaxbSerializationProvider.commaSeperatedStringToClassSet(classLoader, extraClassesString);
for (Class> extraClass : moreExtraClasses) {
logger.debug("Added {} to serialization context.", extraClass.getName());
}
serializationProvider = new JaxbSerializationProvider(moreExtraClasses);
} else {
serializationProvider = new JaxbSerializationProvider();
}
} catch (JMSException jmse) {
throw new KieRemoteServicesInternalError("Unable to check or read JMS message for property.", jmse);
} catch (SerializationException se) {
throw new KieRemoteServicesRuntimeException("Unable to load classes needed for JAXB deserialization.", se);
}
return serializationProvider;
}
private static Message serializeResponse(Session session, String msgId, int serializationType,
SerializationProvider serializationProvider, JaxbCommandsResponse jaxbResponse) {
BytesMessage byteMsg = null;
try {
byteMsg = session.createBytesMessage();
byteMsg.setIntProperty(SERIALIZATION_TYPE_PROPERTY_NAME, serializationType);
String msgStr;
switch (serializationType) {
case JaxbSerializationProvider.JMS_SERIALIZATION_TYPE:
msgStr = (String) serializationProvider.serialize(jaxbResponse);
Collection> extraJaxbClasses = ((JaxbSerializationProvider) serializationProvider).getExtraJaxbClasses();
if (!extraJaxbClasses.isEmpty()) {
String propValue;
try {
propValue = JaxbSerializationProvider.classSetToCommaSeperatedString(extraJaxbClasses);
} catch (SerializationException se) {
throw new KieRemoteServicesRuntimeException("Unable to get class names for extra JAXB classes.", se);
}
byteMsg.setStringProperty(EXTRA_JAXB_CLASSES_PROPERTY_NAME, propValue);
}
break;
default:
throw new KieRemoteServicesRuntimeException("Unknown serialization type when deserializing message " + msgId + ":" + serializationType);
}
byteMsg.writeUTF(msgStr);
} catch (JMSException jmse) {
String errMsg = "Unable to create response message or write to it [msg id: " + msgId + "].";
throw new KieRemoteServicesRuntimeException(errMsg, jmse);
} catch (Exception e) {
String errMsg = "Unable to serialize " + jaxbResponse.getClass().getSimpleName() + " to a String.";
throw new KieRemoteServicesInternalError(errMsg, e);
}
return byteMsg;
}
// Runtime / KieSession / TaskService helper methods --------------------------------------------------------------------------
protected JaxbCommandsResponse jmsProcessJaxbCommandsRequest(JaxbCommandsRequest request) {
// If exceptions are happening here, then there is something REALLY wrong and they should be thrown.
JaxbCommandsResponse jaxbResponse = new JaxbCommandsResponse(request);
List commands = request.getCommands();
if (commands != null) {
UserGroupAdapter userGroupAdapter = null;
try {
for (int i = 0; i < commands.size(); ++i) {
Command> cmd = commands.get(i);
if (!AcceptedCommands.getSet().contains(cmd.getClass())) {
String cmdName = cmd.getClass().getName();
String errMsg = cmdName + " is not a supported command and will not be executed.";
logger.warn( errMsg );
UnsupportedOperationException uoe = new UnsupportedOperationException(errMsg);
jaxbResponse.addException(uoe, i, cmd, FORBIDDEN);
continue;
}
if( cmd instanceof TaskCommand && userGroupAdapter == null ) {
userGroupAdapter = getUserFromMessageAndLookupAndInjectGroups(request.getUserPass());
}
// if the JTA transaction (in HT or the KieSession) doesn't commit, that will cause message reception to be *NOT* acknowledged!
processRequestBean.processCommand(cmd, request, i, jaxbResponse);
}
} finally {
clearUserGroupAdapter(userGroupAdapter);
}
}
if (commands == null || commands.isEmpty()) {
logger.info("Commands request object with no commands sent!");
}
return jaxbResponse;
}
/**
* Retrieves the user/pass info from the message and authenticates it against the underlying JAAS module:
* - Calls {@link RequestMessageBean#getUserPass(Message)} to get the user and password from the {@link Message} instance
* - Calls {@link RequestMessageBean#tryLogin(String[])} to create a {@link LoginContext} and login
* - Calls {@link RequestMessageBean#getGroupsFromSubject(Subject)} to retrieve the Roles info from the JAAS login.
* - Injects the groups information into the underlying framework for use by the human-task code
*
*
* @param msg The JMS {@link Message} received.
*/
private UserGroupAdapter getUserFromMessageAndLookupAndInjectGroups(String [] userPass) {
UserGroupAdapter jmsUserGroupAdapter = null;
try {
if( userPass == null ) {
logger.warn("Unable to retrieve user and password from message: NOT injecting group information.");
return null;
}
Subject msgSubject = tryLogin(userPass);
if( msgSubject == null ) {
logger.warn("Unable to login to JAAS with received user and password.");
return null;
}
List roles = getGroupsFromSubject(msgSubject);
String [] rolesArr = new String[roles.size()];
for( int i = 0; i < rolesArr.length; ++i ) {
rolesArr[i] = roles.get(i).getName();
}
UserGroupAdapter newUserGroupAdapter = new JmsUserGroupAdapter(userPass[0], rolesArr);
JAASUserGroupCallbackImpl.addExternalUserGroupAdapter(newUserGroupAdapter);
jmsUserGroupAdapter = newUserGroupAdapter;
} catch (Exception e) {
logger.warn("Unable to retrieve group information for user in message: " + e.getMessage(), e);
}
return jmsUserGroupAdapter;
}
private void clearUserGroupAdapter(UserGroupAdapter userGroupAdapter) {
if( userGroupAdapter != null ) {
JAASUserGroupCallbackImpl.clearExternalUserGroupAdapter();
}
}
private static final String USERNAME_PROPERTY = "username";
private static final String PASSWORD_PROPERTY = "password";
/**
* Get the user and password information.
*
* @param msg The JMS {@link Message} received.
* @return A String array, with the user and password in that order. In the case that something goes wrong, null is returned.
*/
private String[] getUserPass(Message msg) {
String prop = USERNAME_PROPERTY;
try {
String user = null;
String pass = null;
if( msg.propertyExists(prop) ) {
user = msg.getStringProperty(prop);
}
prop = PASSWORD_PROPERTY;
if( msg.propertyExists(prop) ) {
pass = msg.getStringProperty(prop);
}
if( user != null && pass != null ) {
String [] userPass = { user, pass };
return userPass;
}
} catch(Exception e) {
logger.error( "Unable to retrieve '" + prop + "' from JMS message.", e);
}
return null;
}
/**
* Try to login to the underlying JAAS module via a {@link LoginException}.
*
* @param userPass A String array containing the user and password information.
* @return The logged-in {@link Subject}
* @throws LoginException If something goes wrong when trying to login.
*/
private Subject tryLogin(String[] userPass) throws LoginException {
try {
CallbackHandler handler = new UserPassCallbackHandler(userPass);
LoginContext lc = new LoginContext("kie-jms-login-context", handler);
lc.login();
return lc.getSubject();
} catch( Exception e ) {
logger.error( "Unable to login via JAAS with message supplied user and password", e);
return null;
}
}
/**
* Extracts the list of roles from the subject.
*
* @param subject The JAAS login subject
* @return A list of {@link Principal} objects, that are the role/groups.
*/
private List getGroupsFromSubject(Subject subject) {
List userGroups = new ArrayList();
for (Principal principal : subject.getPrincipals()) {
if (principal instanceof Group && "Roles".equalsIgnoreCase(principal.getName())) {
Enumeration extends Principal> groups = ((Group) principal).members();
while (groups.hasMoreElements()) {
Principal groupPrincipal = (Principal) groups.nextElement();
userGroups.add(groupPrincipal);
}
}
}
return userGroups;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy