/** * Copyright (c) 2009 - 2012 Red Hat, Inc. * * This software is licensed to you under the GNU General Public License, * version 2 (GPLv2). There is NO WARRANTY for this software, express or * implied, including the implied warranties of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 * along with this software; if not, see * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. * * Red Hat trademarks are not licensed under GPLv2. No permission is * granted to use or replicate Red Hat trademarks that are incorporated * in this software or its documentation. */ package org.candlepin.audit; import org.candlepin.common.config.Configuration; import org.candlepin.config.ConfigProperties; import org.candlepin.controller.ModeManager; import org.candlepin.model.Consumer; import org.candlepin.model.Entitlement; import org.candlepin.model.Owner; import org.candlepin.model.Pool; import org.candlepin.model.Rules; import org.candlepin.model.activationkeys.ActivationKey; import org.candlepin.model.dto.Subscription; import org.candlepin.policy.js.compliance.ComplianceStatus; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.inject.Singleton; import org.hornetq.api.core.HornetQException; import org.hornetq.api.core.SimpleString; import org.hornetq.api.core.TransportConfiguration; import org.hornetq.api.core.client.ClientMessage; import org.hornetq.api.core.client.ClientProducer; import org.hornetq.api.core.client.ClientSession; import org.hornetq.api.core.client.ClientSessionFactory; import org.hornetq.api.core.client.HornetQClient; import org.hornetq.api.core.client.ServerLocator; import org.hornetq.core.remoting.impl.invm.InVMConnectorFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.LinkedList; import java.util.List; import java.util.Set; import javax.inject.Inject; /** * EventSink - Queues events to be sent after request/job completes, and handles actual * sending of events on successful job or API request, as well as rollback if either fails. */ @Singleton public class EventSinkImpl implements EventSink { private static Logger log = LoggerFactory.getLogger(EventSinkImpl.class); private EventFactory eventFactory; private ClientSessionFactory factory; private Configuration config; private ObjectMapper mapper; private EventFilter eventFilter; private int largeMsgSize; private ModeManager modeManager; /* * Important use of ThreadLocal here, each Tomcat/Quartz thread gets it's own session * which is reused across invocations. Sessions must have commit or rollback called * on them per request/job. This is handled in EventFilter for the API, and KingpinJob * for quartz jobs. */ private ThreadLocal<ClientSession> sessions = new ThreadLocal<ClientSession>(); private ThreadLocal<ClientProducer> producers = new ThreadLocal<ClientProducer>(); @Inject public EventSinkImpl(EventFilter eventFilter, EventFactory eventFactory, ObjectMapper mapper, Configuration config, ModeManager modeManager) { this.eventFactory = eventFactory; this.mapper = mapper; this.config = config; this.eventFilter = eventFilter; this.modeManager = modeManager; largeMsgSize = config.getInt(ConfigProperties.HORNETQ_LARGE_MSG_SIZE); } /** * Initializes the Singleton from the ContextListener not from the ctor. * @throws Exception thrown if there's a problem creating the session factory. */ @Override public void initialize() throws Exception { factory = createClientSessionFactory(); } protected ClientSessionFactory createClientSessionFactory() throws Exception { ServerLocator locator = HornetQClient.createServerLocatorWithoutHA( new TransportConfiguration(InVMConnectorFactory.class.getName())); locator.setMinLargeMessageSize(largeMsgSize); return locator.createSessionFactory(); } protected ClientSession getClientSession() { ClientSession session = sessions.get(); if (session == null || session.isClosed()) { try { /* * Use a transacted HornetQ session, events will not be dispatched until * commit() is called on it, and a call to rollback() will revert any queued * messages safely and the session is then ready to start over the next time * the thread is used. */ session = factory.createTransactedSession(); } catch (HornetQException e) { throw new RuntimeException(e); } log.debug("Created new HornetQ session."); sessions.set(session); } return session; } protected ClientProducer getClientProducer() { ClientProducer producer = producers.get(); if (producer == null) { try { producer = getClientSession().createProducer(EventSource.QUEUE_ADDRESS); } catch (HornetQException e) { throw new RuntimeException(e); } log.info("Created new HornetQ producer."); producers.set(producer); } return producer; } @Override public List<QueueStatus> getQueueInfo() { List<QueueStatus> results = new LinkedList<QueueStatus>(); try { ClientSession session = getClientSession(); session.start(); for (String listenerClassName : HornetqContextListener.getHornetqListeners(config)) { String queueName = "event." + listenerClassName; long msgCount = session.queueQuery(new SimpleString(queueName)).getMessageCount(); results.add(new QueueStatus(queueName, msgCount)); } } catch (Exception e) { log.error("Error looking up hornetq queue info: ", e); } return results; } /** * Adds an event to the queue to be sent on successful completion of the request or job. * sendEvents() must be called for these events to actually go out. This happens * automatically after each successful REST API request, and KingpingJob. If either * is not successful, rollback() must be called. * * Events are filtered, meaning that some of them might not even get into HornetQ. * Details about the filtering are documented in EventFilter class * * HornetQ transaction actually manages the queue of events to be sent. */ @Override public void queueEvent(Event event) { if (eventFilter.shouldFilter(event)) { log.debug("Filtering event {}", event); return; } modeManager.throwRestEasyExceptionIfInSuspendMode(); log.debug("Queuing event: {}", event); try { ClientSession session = getClientSession(); ClientMessage message = session.createMessage(true); String eventString = mapper.writeValueAsString(event); message.getBodyBuffer().writeString(eventString); // NOTE: not actually send until we commit the session. getClientProducer().send(message); } catch (Exception e) { log.error("Error while trying to send event: " + event, e); } } /** * Dispatch queued events. (if there are any) * * Typically only called after a successful request or job execution. */ @Override public void sendEvents() { try { log.debug("Committing hornetq transaction."); getClientSession().commit(); } catch (Exception e) { // This would be pretty bad, but we always try not to let event errors // interfere with the operation of the overall application. log.error("Error committing hornetq transaction", e); } } @Override public void rollback() { log.warn("Rolling back hornetq transaction."); try { ClientSession session = getClientSession(); session.rollback(); } catch (HornetQException e) { log.error("Error rolling back hornetq transaction", e); } } public void emitConsumerCreated(Consumer newConsumer) { Event e = eventFactory.consumerCreated(newConsumer); queueEvent(e); } public void emitOwnerCreated(Owner newOwner) { Event e = eventFactory.ownerCreated(newOwner); queueEvent(e); } public void emitOwnerMigrated(Owner owner) { Event e = eventFactory.ownerMigrated(owner); queueEvent(e); } public void emitPoolCreated(Pool newPool) { Event e = eventFactory.poolCreated(newPool); queueEvent(e); } public void emitExportCreated(Consumer consumer) { Event e = eventFactory.exportCreated(consumer); queueEvent(e); } public void emitImportCreated(Owner owner) { Event e = eventFactory.importCreated(owner); queueEvent(e); } public void emitActivationKeyCreated(ActivationKey key) { Event e = eventFactory.activationKeyCreated(key); queueEvent(e); } public void emitSubscriptionExpired(Subscription subscription) { Event e = eventFactory.subscriptionExpired(subscription); queueEvent(e); } @Override public void emitRulesModified(Rules oldRules, Rules newRules) { queueEvent(eventFactory.rulesUpdated(oldRules, newRules)); } @Override public void emitRulesDeleted(Rules rules) { queueEvent(eventFactory.rulesDeleted(rules)); } @Override public void emitCompliance(Consumer consumer, Set<Entitlement> entitlements, ComplianceStatus compliance) { queueEvent(eventFactory.complianceCreated(consumer, entitlements, compliance)); } }