All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.killbill.bus.DefaultPersistentBus Maven / Gradle / Ivy

There is a newer version: 0.40.13
Show newest version
/*
 * Copyright 2010-2013 Ning, Inc.
 * Copyright 2014-2018 Groupon, Inc
 * Copyright 2014-2018 The Billing Project, LLC
 *
 * The Billing Project licenses this file to you 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.killbill.bus;

import java.sql.Connection;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Named;
import javax.sql.DataSource;

import org.joda.time.DateTime;
import org.killbill.CreatorName;
import org.killbill.bus.api.BusEvent;
import org.killbill.bus.api.BusEventWithMetadata;
import org.killbill.bus.api.PersistentBus;
import org.killbill.bus.api.PersistentBusConfig;
import org.killbill.bus.dao.BusEventModelDao;
import org.killbill.bus.dao.PersistentBusSqlDao;
import org.killbill.bus.dispatching.BusCallableCallback;
import org.killbill.clock.Clock;
import org.killbill.clock.DefaultClock;
import org.killbill.commons.jdbi.notification.DatabaseTransactionNotificationApi;
import org.killbill.commons.profiling.Profiling;
import org.killbill.commons.profiling.ProfilingFeature;
import org.killbill.queue.DBBackedQueue;
import org.killbill.queue.DBBackedQueue.ReadyEntriesWithMetrics;
import org.killbill.queue.DBBackedQueueWithInflightQueue;
import org.killbill.queue.DBBackedQueueWithPolling;
import org.killbill.queue.DefaultQueueLifecycle;
import org.killbill.queue.InTransaction;
import org.killbill.queue.api.PersistentQueueConfig.PersistentQueueMode;
import org.killbill.queue.api.QueueEvent;
import org.killbill.queue.dao.EventEntryModelDao;
import org.killbill.queue.dispatching.BlockingRejectionExecutionHandler;
import org.killbill.queue.dispatching.CallableCallbackBase;
import org.killbill.queue.dispatching.Dispatcher;
import org.skife.config.ConfigurationObjectFactory;
import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.IDBI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.eventbus.EventBusThatThrowsException;

public class DefaultPersistentBus extends DefaultQueueLifecycle implements PersistentBus {

    private static final Logger log = LoggerFactory.getLogger(DefaultPersistentBus.class);

    private final DBI dbi;
    private final EventBusThatThrowsException eventBusDelegate;
    private final DBBackedQueue dao;
    private final Clock clock;
    private final PersistentBusConfig config;
    private final Profiling, RuntimeException> prof;
    private final BusReaper reaper;

    private final Dispatcher dispatcher;

    // Time it takes to handle the bus request (going through multiple handles potentially)
    private final Timer busHandlersProcessingTime;

    private final AtomicBoolean isInitialized;
    private final AtomicBoolean isStarted;
    private final String dbBackedQId;

    private final BusCallableCallback busCallableCallback;

    private static final class EventBusDelegate extends EventBusThatThrowsException {

        public EventBusDelegate(final String busName) {
            super(busName);
        }
    }

    @Inject
    public DefaultPersistentBus(@Named(QUEUE_NAME) final IDBI dbi, final Clock clock, final PersistentBusConfig config, final MetricRegistry metricRegistry, final DatabaseTransactionNotificationApi databaseTransactionNotificationApi) {
        super(config.getTableName(), config, metricRegistry);
        this.dbi = (DBI) dbi;
        this.clock = clock;
        this.config = config;
        this.dbBackedQId = config.getTableName();
        this.dao = config.getPersistentQueueMode() == PersistentQueueMode.STICKY_EVENTS ?
                   new DBBackedQueueWithInflightQueue(clock, dbi, PersistentBusSqlDao.class, config, dbBackedQId, metricRegistry, databaseTransactionNotificationApi) :
                   new DBBackedQueueWithPolling(clock, dbi, PersistentBusSqlDao.class, config, dbBackedQId, metricRegistry);

        this.prof = new Profiling, RuntimeException>();
        final ThreadFactory busThreadFactory = new ThreadFactory() {
            @Override
            public Thread newThread(final Runnable r) {
                return new Thread(new ThreadGroup(EVENT_BUS_GROUP_NAME),
                                  r,
                                  config.getTableName() + "-th");
            }
        };

        this.busHandlersProcessingTime = metricRegistry.timer(MetricRegistry.name(DefaultPersistentBus.class, dbBackedQId, "busHandlersProcessingTime"));

        this.eventBusDelegate = new EventBusDelegate("Killbill EventBus");
        this.isInitialized = new AtomicBoolean(false);
        this.isStarted = new AtomicBoolean(false);
        this.reaper = new BusReaper(this.dao, config, clock);

        this.busCallableCallback = new BusCallableCallback(this);
        this.dispatcher = new Dispatcher<>(1, config, 10, TimeUnit.MINUTES, new LinkedBlockingQueue(config.getEventQueueCapacity()), busThreadFactory, new BlockingRejectionExecutionHandler(),
                                           clock, busCallableCallback, this);

    }

    public DefaultPersistentBus(final DataSource dataSource, final Properties properties) {
        this(InTransaction.buildDDBI(dataSource), new DefaultClock(), new ConfigurationObjectFactory(properties).buildWithReplacements(PersistentBusConfig.class, ImmutableMap.of("instanceName", "main")),
             new MetricRegistry(), new DatabaseTransactionNotificationApi());
    }

    @Override
    public void start() {

        if (config.isProcessingOff()) {
            log.warn("PersistentBus processing is off, does not start");
            return;
        }

        if (isInitialized.compareAndSet(false, true)) {
            dao.initialize();
            dispatcher.start();
            startQueue();

            if (config.getPersistentQueueMode() == PersistentQueueMode.STICKY_POLLING || config.getPersistentQueueMode() == PersistentQueueMode.STICKY_EVENTS) {
                reaper.start();
            }
            isStarted.set(true);
        }

    }

    @Override
    public void stop() {
        if (isStarted.compareAndSet(true, false)) {
            isInitialized.set(false);
            dao.close();
            stopQueue();
            dispatcher.stop();
            reaper.stop();
        }
    }

    @Override
    public DispatchResultMetrics doDispatchEvents() {
        final ReadyEntriesWithMetrics eventsWithMetrics = dao.getReadyEntries();
        final List events = eventsWithMetrics.getEntries();
        if (events.isEmpty()) {
            return new DispatchResultMetrics(0, eventsWithMetrics.getTime());
        }
        log.debug("Bus events from {} to process: {}", config.getTableName(), events);

        long ini = System.nanoTime();
        for (final BusEventModelDao cur : events) {
            dispatcher.dispatch(cur);
        }
        return new DispatchResultMetrics(events.size(), (System.nanoTime() - ini) + eventsWithMetrics.getTime());
    }

    @Override
    public void doProcessCompletedEvents(final Iterable completed) {
        busCallableCallback.moveCompletedOrFailedEvents((Iterable) completed);
    }

    @Override
    public void doProcessRetriedEvents(final Iterable retried) {
        Iterator it = retried.iterator();
        while (it.hasNext()) {
            BusEventModelDao cur = (BusEventModelDao) it.next();
            busCallableCallback.updateRetriedEvents(cur);
        }
    }

    @Override
    public boolean isStarted() {
        return isStarted.get();
    }

    @Override
    public void register(final Object handlerInstance) throws EventBusException {
        if (isStarted.get()) {
            eventBusDelegate.register(handlerInstance);
        } else {
            log.warn("Attempting to register handler " + handlerInstance + " in a non initialized bus");
        }
    }

    @Override
    public void unregister(final Object handlerInstance) throws EventBusException {
        if (isStarted.get()) {
            eventBusDelegate.unregister(handlerInstance);
        } else {
            log.warn("Attempting to unregister handler " + handlerInstance + " in a non initialized bus");
        }
    }

    @Override
    public void post(final BusEvent event) throws EventBusException {
        try {
            if (isStarted.get()) {
                final String json = objectMapper.writeValueAsString(event);
                final BusEventModelDao entry = new BusEventModelDao(CreatorName.get(), clock.getUTCNow(), event.getClass().getName(), json,
                                                                    event.getUserToken(), event.getSearchKey1(), event.getSearchKey2());
                dao.insertEntry(entry);

            } else {
                log.warn("Attempting to post event " + event + " in a non initialized bus");
            }
        } catch (final Exception e) {
            log.error("Failed to post BusEvent " + event, e);
        }
    }

    @Override
    public void postFromTransaction(final BusEvent event, final Connection connection) throws EventBusException {
        if (!isStarted.get()) {
            log.warn("Attempting to post event " + event + " in a non initialized bus");
            return;
        }

        final String json;
        try {
            json = objectMapper.writeValueAsString(event);
        } catch (final JsonProcessingException e) {
            log.warn("Unable to serialize event " + event, e);
            return;
        }

        final BusEventModelDao entry = new BusEventModelDao(CreatorName.get(),
                                                            clock.getUTCNow(),
                                                            event.getClass().getName(),
                                                            json,
                                                            event.getUserToken(),
                                                            event.getSearchKey1(),
                                                            event.getSearchKey2());

        final InTransaction.InTransactionHandler handler = new InTransaction.InTransactionHandler() {

            @Override
            public Void withSqlDao(final PersistentBusSqlDao transactional) throws Exception {
                dao.insertEntryFromTransaction(transactional, entry);
                return null;
            }
        };

        InTransaction.execute(dbi, connection, handler, PersistentBusSqlDao.class);
    }

    @Override
    public  Iterable> getAvailableBusEventsForSearchKeys(final Long searchKey1, final Long searchKey2) {
        return getAvailableBusEventsForSearchKeysInternal((PersistentBusSqlDao) dao.getSqlDao(), null, searchKey1, searchKey2);
    }

    @Override
    public  Iterable> getAvailableBusEventsFromTransactionForSearchKeys(final Long searchKey1, final Long searchKey2, final Connection connection) {
        final InTransaction.InTransactionHandler>> handler = new InTransaction.InTransactionHandler>>() {
            @Override
            public Iterable> withSqlDao(final PersistentBusSqlDao transactional) throws Exception {
                return getAvailableBusEventsForSearchKeysInternal(transactional, null, searchKey1, searchKey2);
            }
        };
        return InTransaction.execute(dbi, connection, handler, PersistentBusSqlDao.class);
    }

    @Override
    public  Iterable> getAvailableBusEventsForSearchKey2(final DateTime maxCreatedDate, final Long searchKey2) {
        return getAvailableBusEventsForSearchKeysInternal((PersistentBusSqlDao) dao.getSqlDao(), maxCreatedDate, null, searchKey2);
    }

    @Override
    public  Iterable> getAvailableBusEventsFromTransactionForSearchKey2(final DateTime maxCreatedDate, final Long searchKey2, final Connection connection) {
        final InTransaction.InTransactionHandler>> handler = new InTransaction.InTransactionHandler>>() {
            @Override
            public Iterable> withSqlDao(final PersistentBusSqlDao transactional) throws Exception {
                return getAvailableBusEventsForSearchKeysInternal(transactional, maxCreatedDate, null, searchKey2);
            }
        };
        return InTransaction.execute(dbi, connection, handler, PersistentBusSqlDao.class);
    }

    @Override
    public  Iterable> getInProcessingBusEvents() {
        return toBusEventWithMetadata(dao.getSqlDao().getInProcessingEntries(config.getTableName()));
    }

    @Override
    public  Iterable> getAvailableOrInProcessingBusEventsForSearchKeys(final Long searchKey1, final Long searchKey2) {
        return getAvailableOrInProcessingBusEventsForSearchKeysInternal((PersistentBusSqlDao) dao.getSqlDao(), null, searchKey1, searchKey2);
    }

    @Override
    public  Iterable> getAvailableOrInProcessingBusEventsFromTransactionForSearchKeys(final Long searchKey1, final Long searchKey2, final Connection connection) {
        final InTransaction.InTransactionHandler>> handler = new InTransaction.InTransactionHandler>>() {
            @Override
            public Iterable> withSqlDao(final PersistentBusSqlDao transactional) throws Exception {
                return getAvailableOrInProcessingBusEventsForSearchKeysInternal(transactional, null, searchKey1, searchKey2);
            }
        };
        return InTransaction.execute(dbi, connection, handler, PersistentBusSqlDao.class);
    }

    @Override
    public  Iterable> getAvailableOrInProcessingBusEventsForSearchKey2(final DateTime maxCreatedDate, final Long searchKey2) {
        return getAvailableOrInProcessingBusEventsForSearchKeysInternal((PersistentBusSqlDao) dao.getSqlDao(), maxCreatedDate, null, searchKey2);
    }

    @Override
    public  Iterable> getAvailableOrInProcessingBusEventsFromTransactionForSearchKey2(final DateTime maxCreatedDate, final Long searchKey2, final Connection connection) {
        final InTransaction.InTransactionHandler>> handler = new InTransaction.InTransactionHandler>>() {
            @Override
            public Iterable> withSqlDao(final PersistentBusSqlDao transactional) throws Exception {
                return getAvailableOrInProcessingBusEventsForSearchKeysInternal(transactional, maxCreatedDate, null, searchKey2);
            }
        };
        return InTransaction.execute(dbi, connection, handler, PersistentBusSqlDao.class);
    }

    @Override
    public  Iterable> getHistoricalBusEventsForSearchKeys(final Long searchKey1, final Long searchKey2) {
        return getHistoricalBusEventsForSearchKeysInternal((PersistentBusSqlDao) dao.getSqlDao(), null, searchKey1, searchKey2);
    }

    @Override
    public  Iterable> getHistoricalBusEventsForSearchKey2(final DateTime minCreatedDate, final Long searchKey2) {
        return getHistoricalBusEventsForSearchKeysInternal((PersistentBusSqlDao) dao.getSqlDao(), minCreatedDate, null, searchKey2);
    }

    @Override
    public long getNbReadyEntries(final DateTime maxCreatedDate) {
        return dao.getNbReadyEntries(maxCreatedDate.toDate());
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("DefaultPersistentBus{");
        sb.append("dbBackedQId='").append(dbBackedQId).append('\'');
        sb.append('}');
        return sb.toString();
    }

    public void dispatchBusEventWithMetrics(final QueueEvent event) throws com.google.common.eventbus.EventBusException {
        final Timer.Context dispatchTimerContext = busHandlersProcessingTime.time();
        try {
            eventBusDelegate.postWithException(event);
        } finally {
            dispatchTimerContext.stop();
        }
    }

    private  Iterable> getAvailableBusEventsForSearchKeysInternal(final PersistentBusSqlDao transactionalDao, @Nullable final DateTime maxCreatedDate, @Nullable final Long searchKey1, final Long searchKey2) {
        final Iterable entries = getReadyQueueEntriesForSearchKeysWithProfiling(transactionalDao, maxCreatedDate, searchKey1, searchKey2);
        return toBusEventWithMetadata(entries);
    }

    private  Iterable> getAvailableOrInProcessingBusEventsForSearchKeysInternal(final PersistentBusSqlDao transactionalDao, @Nullable final DateTime maxCreatedDate, @Nullable final Long searchKey1, final Long searchKey2) {
        final Iterable entries = getReadyOrInProcessingQueueEntriesForSearchKeysWithProfiling(transactionalDao, maxCreatedDate, searchKey1, searchKey2);
        return toBusEventWithMetadata(entries);
    }

    private  Iterable> getHistoricalBusEventsForSearchKeysInternal(final PersistentBusSqlDao transactionalDao, @Nullable final DateTime minCreatedDate, @Nullable final Long searchKey1, final Long searchKey2) {
        final Iterable entries = getHistoricalQueueEntriesForSearchKeysWithProfiling(transactionalDao, minCreatedDate, searchKey1, searchKey2);
        return toBusEventWithMetadata(entries);
    }

    private Iterable getReadyQueueEntriesForSearchKeysWithProfiling(final PersistentBusSqlDao transactionalDao, @Nullable final DateTime maxCreatedDate, @Nullable final Long searchKey1, final Long searchKey2) {
        return prof.executeWithProfiling(ProfilingFeature.ProfilingFeatureType.DAO, "DAO:PersistentBusSqlDao:getReadyQueueEntriesForSearchKeys", new Profiling.WithProfilingCallback, RuntimeException>() {
            @Override
            public Iterable execute() throws RuntimeException {
                return new Iterable() {
                    @Override
                    public Iterator iterator() {
                        return searchKey1 != null ?
                               transactionalDao.getReadyQueueEntriesForSearchKeys(searchKey1, searchKey2, config.getTableName()) :
                               transactionalDao.getReadyQueueEntriesForSearchKey2(maxCreatedDate, searchKey2, config.getTableName());
                    }
                };
            }
        });
    }

    private Iterable getReadyOrInProcessingQueueEntriesForSearchKeysWithProfiling(final PersistentBusSqlDao transactionalDao, @Nullable final DateTime maxCreatedDate, @Nullable final Long searchKey1, final Long searchKey2) {
        return prof.executeWithProfiling(ProfilingFeature.ProfilingFeatureType.DAO, "DAO:PersistentBusSqlDao:getReadyOrInProcessingQueueEntriesForSearchKeys", new Profiling.WithProfilingCallback, RuntimeException>() {
            @Override
            public Iterable execute() throws RuntimeException {
                return new Iterable() {
                    @Override
                    public Iterator iterator() {
                        return searchKey1 != null ?
                               transactionalDao.getReadyOrInProcessingQueueEntriesForSearchKeys(searchKey1, searchKey2, config.getTableName()) :
                               transactionalDao.getReadyOrInProcessingQueueEntriesForSearchKey2(maxCreatedDate, searchKey2, config.getTableName());
                    }
                };
            }
        });
    }

    private Iterable getHistoricalQueueEntriesForSearchKeysWithProfiling(final PersistentBusSqlDao transactionalDao, @Nullable final DateTime minCreatedDate, @Nullable final Long searchKey1, final Long searchKey2) {
        return prof.executeWithProfiling(ProfilingFeature.ProfilingFeatureType.DAO, "DAO:PersistentBusSqlDao:getHistoricalQueueEntriesForSearchKeys", new Profiling.WithProfilingCallback, RuntimeException>() {
            @Override
            public Iterable execute() throws RuntimeException {
                return new Iterable() {
                    @Override
                    public Iterator iterator() {
                        return searchKey1 != null ?
                               transactionalDao.getHistoricalQueueEntriesForSearchKeys(searchKey1, searchKey2, config.getHistoryTableName()) :
                               transactionalDao.getHistoricalQueueEntriesForSearchKey2(minCreatedDate, searchKey2, config.getHistoryTableName());
                    }
                };
            }
        });
    }

    private  Iterable> toBusEventWithMetadata(final Iterable entries) {
        return Iterables.>transform(entries,
                                                                              new Function>() {
                                                                                  @Override
                                                                                  public BusEventWithMetadata apply(final BusEventModelDao input) {
                                                                                      return toBusEventWithMetadata(input);
                                                                                  }
                                                                              });
    }

    private  BusEventWithMetadata toBusEventWithMetadata(final BusEventModelDao entry) {
        final T event = CallableCallbackBase.deserializeEvent(entry, objectMapper);
        return new BusEventWithMetadata(entry.getRecordId(),
                                           entry.getUserToken(),
                                           entry.getCreatedDate(),
                                           entry.getSearchKey1(),
                                           entry.getSearchKey2(),
                                           event);
    }

    public DBBackedQueue getDao() {
        return dao;
    }

    public Clock getClock() {
        return clock;
    }

    public PersistentBusConfig getConfig() {
        return config;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy