/* * 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.jbpm.process.audit.jms; import java.util.List; import javax.jms.Connection; import javax.jms.ConnectionFactory; import javax.jms.JMSException; import javax.jms.MessageProducer; import javax.jms.Queue; import javax.jms.Session; import javax.jms.TextMessage; import org.jbpm.process.audit.AbstractAuditLogger; import org.jbpm.process.audit.NodeInstanceLog; import org.jbpm.process.audit.ProcessInstanceLog; import org.jbpm.process.audit.variable.ProcessIndexerManager; import org.jbpm.workflow.instance.impl.NodeInstanceImpl; import org.kie.api.event.process.ProcessCompletedEvent; import org.kie.api.event.process.ProcessNodeLeftEvent; import org.kie.api.event.process.ProcessNodeTriggeredEvent; import org.kie.api.event.process.ProcessStartedEvent; import org.kie.api.event.process.ProcessVariableChangedEvent; import org.kie.api.runtime.KieSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.thoughtworks.xstream.XStream; /** * Asynchronous log producer that puts audit log events into JMS queue. * It expects to have following objects available before it is fully operational: * <ul> * <li>ConnectionFactory - used to create jMS objects required to send a message</li> * <li>Queue - JMS destination where messages should be placed</li> * </ul> * * It sends TextMessages with content of *Log classes (ProcessInstanceLog, * NodeInstanceLog, VaraiableInstanceLog) serialized by Xstream. * Such serialization allows: * <ul> * <li>use of message selectors to filter which types of events should be processed by different consumer</li> * <li>use any consumer to process messages - does not have to be default one</li> * <li>use content based routing in more advanced scenarios</li> * </ul> * * Default receiver is <code>AsyncAuditLogReceiver</code> class */ public class AsyncAuditLogProducer extends AbstractAuditLogger { private static final Logger logger = LoggerFactory.getLogger(AsyncAuditLogProducer.class); private ConnectionFactory connectionFactory; private Queue queue; private boolean transacted = true; private ProcessIndexerManager indexManager = ProcessIndexerManager.get(); public AsyncAuditLogProducer() { } public AsyncAuditLogProducer(KieSession session, boolean transacted) { super(session); this.transacted = transacted; session.addEventListener(this); } public ConnectionFactory getConnectionFactory() { return connectionFactory; } public void setConnectionFactory(ConnectionFactory connectionFactory) { this.connectionFactory = connectionFactory; } public Queue getQueue() { return queue; } public void setQueue(Queue queue) { this.queue = queue; } @Override public void beforeNodeTriggered(ProcessNodeTriggeredEvent event) { NodeInstanceLog log = (NodeInstanceLog) builder.buildEvent(event); sendMessage(log, BEFORE_NODE_ENTER_EVENT_TYPE); ((NodeInstanceImpl) event.getNodeInstance()).getMetaData().put("NodeInstanceLog", log); } @Override public void afterNodeLeft(ProcessNodeLeftEvent event) { NodeInstanceLog log = (NodeInstanceLog) builder.buildEvent(event, null); sendMessage(log, AFTER_NODE_LEFT_EVENT_TYPE); } @Override public void afterVariableChanged(ProcessVariableChangedEvent event) { List<org.kie.api.runtime.manager.audit.VariableInstanceLog> variables = indexManager.index(getBuilder(), event); for (org.kie.api.runtime.manager.audit.VariableInstanceLog log : variables) { sendMessage(log, AFTER_VAR_CHANGE_EVENT_TYPE); } } @Override public void beforeProcessStarted(ProcessStartedEvent event) { ProcessInstanceLog log = (ProcessInstanceLog) builder.buildEvent(event); sendMessage(log, BEFORE_START_EVENT_TYPE); } @Override public void afterProcessCompleted(ProcessCompletedEvent event) { ProcessInstanceLog log = (ProcessInstanceLog) builder.buildEvent(event, null); sendMessage(log, AFTER_COMPLETE_EVENT_TYPE); } @Override public void afterNodeTriggered(ProcessNodeTriggeredEvent event) { // trigger this to record some of the data (like work item id) after activity was triggered NodeInstanceLog log = (NodeInstanceLog) ((NodeInstanceImpl) event.getNodeInstance()).getMetaData().get("NodeInstanceLog"); NodeInstanceLog logUpdated = (NodeInstanceLog) builder.buildEvent(event, log); if (logUpdated != null) { sendMessage(log, AFTER_NODE_ENTER_EVENT_TYPE); } } @Override public void beforeNodeLeft(ProcessNodeLeftEvent event) { } @Override public void beforeVariableChanged(ProcessVariableChangedEvent event) { } @Override public void afterProcessStarted(ProcessStartedEvent event) { } @Override public void beforeProcessCompleted(ProcessCompletedEvent event) { } protected void sendMessage(Object messageContent, Integer eventType) { if (connectionFactory == null && queue == null) { throw new IllegalStateException("ConnectionFactory and Queue cannot be null"); } Connection queueConnection = null; Session queueSession = null; MessageProducer producer = null; try { queueConnection = connectionFactory.createConnection(); queueSession = queueConnection.createSession(transacted, Session.AUTO_ACKNOWLEDGE); XStream xstream = new XStream(); String eventXml = xstream.toXML(messageContent); TextMessage message = queueSession.createTextMessage(eventXml); message.setIntProperty("EventType", eventType); producer = queueSession.createProducer(queue); producer.send(message); } catch (Exception e) { throw new RuntimeException("Error when sending JMS message with working memory event", e); } finally { if (producer != null) { try { producer.close(); } catch (JMSException e) { logger.warn("Error when closing producer", e); } } if (queueSession != null) { try { queueSession.close(); } catch (JMSException e) { logger.warn("Error when closing queue session", e); } } if (queueConnection != null) { try { queueConnection.close(); } catch (JMSException e) { logger.warn("Error when closing queue connection", e); } } } } public boolean isTransacted() { return transacted; } public void setTransacted(boolean transacted) { this.transacted = transacted; } }