/**
 * Copyright (c) 2013-2016, The SeedStack authors <http://seedstack.org>
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
package org.seedstack.business.internal.event;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.Multimap;
import com.google.inject.Injector;
import org.seedstack.business.Event;
import org.seedstack.business.EventHandler;
import org.seedstack.business.EventService;
import org.seedstack.business.domain.events.EventErrorCodes;
import org.seedstack.seed.SeedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import java.util.Collection;

/**
 * Internal implementation of EventService.
 *
 * @author pierre.thirouin@ext.mpsa.com
 */
class EventServiceInternal implements EventService {

    private static final Logger LOGGER = LoggerFactory.getLogger(EventServiceInternal.class);

    private final ImmutableListMultimap<Class<? extends Event>, Class<? extends EventHandler>> eventHandlerClassesByEvent;

    private final Injector injector;

    private static final ThreadLocal<Multimap<Class<? extends Event>, Event>> context = new ThreadLocal<Multimap<Class<? extends Event>, Event>>() {
        @Override
        protected Multimap<Class<? extends Event>, Event> initialValue() {
            return ArrayListMultimap.create();
        }
    };

    @Inject
    EventServiceInternal(Injector injector, Multimap<Class<? extends Event>, Class<? extends EventHandler>> eventHandlerClassesByEvent) {
        this.injector = injector;
        this.eventHandlerClassesByEvent = ImmutableListMultimap.copyOf(eventHandlerClassesByEvent);
    }

    @Override
    public <E extends Event> void fire(E event) {
        LOGGER.debug("Synchronously fired {}", event.getClass().getName());
        for (Class<? extends Event> eventClass : eventHandlerClassesByEvent.keys()) {
            if (eventClass.isAssignableFrom(event.getClass())) {
                checkCyclicCall(eventClass, event);
                Multimap<Class<? extends Event>, Event> currentEventClasses = context.get();

                boolean isFirstCall = currentEventClasses.isEmpty();

                context.get().put(eventClass, event);
                try {
                    notifyHandlers(eventClass, event);
                } catch (Exception e) {
                    throw SeedException.wrap(e, EventErrorCodes.HANDLER_EXECUTION_FAILED).put("event", eventClass);
                } finally {
                    if (isFirstCall) {
                        context.remove();
                    }
                }
            }
        }
    }

    private void checkCyclicCall(Class<? extends Event> eventClass, Event event) {
        if (context.get().get(eventClass).contains(event)) {
            throw SeedException.createNew(EventErrorCodes.CYCLE_WAS_DETECTED_IN_FIRED_EVENT).put("event", eventClass);
        }
    }

    @SuppressWarnings("unchecked")
    private <E extends Event> void notifyHandlers(Class<? extends E> eventClass, E event) {
        Collection<Class<? extends EventHandler>> eventHandlers = eventHandlerClassesByEvent.get(eventClass);
        for (Class<? extends EventHandler> eventHandlerClass : eventHandlers) {
            LOGGER.debug("Notify handler {}", eventHandlerClass.getName());
            EventHandler eventHandler = injector.getInstance(eventHandlerClass);
            eventHandler.handle(event);
        }
    }
}

@@ -2082,16 +2082,29 @@
 t.keys()
+.elementSet()
 ) %7B%0A    

Fix - EventService firing event n times