de.metas.procurement.webui.event.MFEventBus.java Source code

Java tutorial

Introduction

Here is the source code for de.metas.procurement.webui.event.MFEventBus.java

Source

package de.metas.procurement.webui.event;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutionException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import com.google.gwt.thirdparty.guava.common.cache.CacheBuilder;
import com.google.gwt.thirdparty.guava.common.cache.CacheLoader;
import com.google.gwt.thirdparty.guava.common.cache.LoadingCache;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
import com.google.gwt.thirdparty.guava.common.eventbus.AsyncEventBus;
import com.google.gwt.thirdparty.guava.common.eventbus.EventBus;
import com.google.gwt.thirdparty.guava.common.eventbus.Subscribe;
import com.google.gwt.thirdparty.guava.common.eventbus.SubscriberExceptionContext;
import com.google.gwt.thirdparty.guava.common.eventbus.SubscriberExceptionHandler;
import com.vaadin.ui.UI;

import de.metas.procurement.webui.MFProcurementUI;

/*
 * #%L
 * metasfresh-procurement-webui
 * %%
 * Copyright (C) 2016 metas GmbH
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program. If not, see
 * <http://www.gnu.org/licenses/gpl-2.0.html>.
 * #L%
 */

@Service
public class MFEventBus {
    private final transient Logger logger = LoggerFactory.getLogger(getClass());

    private final EventBus eventBus;

    private final LoadingCache<IApplicationEventListener, EventBusSubscriber> listener2subscribers = CacheBuilder
            .newBuilder().build(new CacheLoader<IApplicationEventListener, EventBusSubscriber>() {
                @Override
                public EventBusSubscriber load(final IApplicationEventListener listener) throws Exception {
                    return new EventBusSubscriber(listener);
                }

            });

    @Autowired
    public MFEventBus(final TaskExecutor taskExecutor) {
        super();

        eventBus = new AsyncEventBus(taskExecutor, new SubscriberExceptionHandler() {

            @Override
            public void handleException(final Throwable exception, final SubscriberExceptionContext context) {
                onEventBusException(exception, context);

            }
        });

        eventBus.register(this);
    }

    private void onEventBusException(final Throwable exception, final SubscriberExceptionContext context) {
        logger.warn("Failed handling event:" + "\n Event: {}" + "\n Method: {}" //
                , context.getEvent(), context.getSubscriberMethod() //
                , exception //
        );
    }

    public void register(final IApplicationEventListener listener) {
        EventBusSubscriber subscriber = listener2subscribers.getIfPresent(listener);
        if (subscriber != null) {
            // already registered
            return;
        }

        try {
            subscriber = listener2subscribers.get(listener);
            eventBus.register(subscriber);
        } catch (final ExecutionException e) {
            throw new RuntimeException("Failed creating subscriber for " + listener, e.getCause());
        }
    }

    public void unregister(final IApplicationEventListener listener) {
        if (listener == null) {
            return;
        }

        final EventBusSubscriber subscriber = listener2subscribers.getIfPresent(listener);
        if (subscriber == null) {
            // not registered
            return;
        }

        listener2subscribers.invalidate(listener);
        eventBus.unregister(subscriber);
    }

    public void unregisterAllExpired() {
        for (final EventBusSubscriber subscriber : new ArrayList<>(listener2subscribers.asMap().values())) {
            subscriber.checkExpiredAndGetUI();
        }
    }

    public void post(final IApplicationEvent event) {
        final PostAfterCommitCollector postAfterCommit = getPostAfterCommit();
        if (postAfterCommit != null) {
            postAfterCommit.add(event);
        } else {
            eventBus.post(event);
        }
    }

    private final class EventBusSubscriber {
        private final IApplicationEventListener delegate;
        private boolean expired = false;

        private EventBusSubscriber(final IApplicationEventListener listener) {
            super();
            delegate = listener;
        }

        private final UI checkExpiredAndGetUI() {
            if (expired) {
                return null;
            }

            final UI listenerUI = delegate.getUI();
            if (listenerUI == null || listenerUI.isClosing()) {
                // listener expired
                expireNow();
                return null;
            }

            return listenerUI;
        }

        private final void expireNow() {
            expired = true;
            unregister(delegate);
        }

        private final void executeInUI(final IApplicationEvent event, final Runnable runnable) {
            if (event == null) {
                // shall not happen
                return;
            }

            final UI listenerUI = checkExpiredAndGetUI();
            if (listenerUI == null) {
                // expired
                return;
            }

            // Skip events which are not for our UI
            final String bpartner_uuid = MFProcurementUI.getBpartner_uuid(listenerUI);
            if (!Objects.equals(bpartner_uuid, event.getBpartner_uuid())) {
                return;
            }

            final UI currentUI = UI.getCurrent();
            if (currentUI != listenerUI) {
                listenerUI.access(runnable);
            } else {
                runnable.run();
            }
        }

        @Subscribe
        public void onContractChanged(final ContractChangedEvent event) {
            executeInUI(event, new Runnable() {

                @Override
                public void run() {
                    delegate.onContractChanged(event);
                }
            });
        }

        @Subscribe
        public void onRfqChanged(final RfqChangedEvent event) {
            executeInUI(event, new Runnable() {

                @Override
                public void run() {
                    delegate.onRfqChanged(event);
                }
            });
        }

        @Subscribe
        public void onProductSupplyChanged(final ProductSupplyChangedEvent event) {
            executeInUI(event, new Runnable() {

                @Override
                public void run() {
                    delegate.onProductSupplyChanged(event);
                }
            });
        }

    }

    private final PostAfterCommitCollector getPostAfterCommit() {
        if (!TransactionSynchronizationManager.isActualTransactionActive()) {
            return null;
        }

        PostAfterCommitCollector instance = null;
        for (final TransactionSynchronization sync : TransactionSynchronizationManager.getSynchronizations()) {
            if (sync instanceof PostAfterCommitCollector) {
                instance = (PostAfterCommitCollector) sync;
                logger.debug("Found PostAfterCommitCollector instance: {}", instance);
            }
        }

        if (instance == null) {
            instance = new PostAfterCommitCollector();
            TransactionSynchronizationManager.registerSynchronization(instance);

            logger.debug("Registered synchronization: {}", instance);
        }

        return instance;
    }

    private final class PostAfterCommitCollector extends TransactionSynchronizationAdapter {
        private final List<Object> _events = new ArrayList<>();

        public synchronized void add(final Object event) {
            _events.add(event);
        }

        private synchronized List<Object> getAndClearEvents() {
            if (_events.isEmpty()) {
                return ImmutableList.of();
            }

            final List<Object> eventsToPost = new ArrayList<>(_events);
            _events.clear();
            return eventsToPost;
        }

        @Override
        public void afterCommit() {
            final List<Object> eventsToPost = getAndClearEvents();
            if (eventsToPost.isEmpty()) {
                return;
            }

            logger.debug("Posting events after commit: {}", eventsToPost);
            for (final Object event : eventsToPost) {
                eventBus.post(event);
            }
        }
    }

}