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

org.jboss.weld.event.ObserverNotifier Maven / Gradle / Ivy

Go to download

This jar bundles all the bits of Weld and CDI required for running in a Servlet container.

There is a newer version: 6.0.0.Beta4
Show newest version
/*
 * JBoss, Home of Professional Open Source
 * Copyright 2012, Red Hat, Inc., and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * Licensed 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.jboss.weld.event;

import static org.jboss.weld.util.reflection.Reflections.cast;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

import javax.enterprise.event.NotificationOptions;
import javax.enterprise.event.ObserverException;
import javax.enterprise.inject.spi.EventMetadata;
import javax.enterprise.inject.spi.ObserverMethod;

import org.jboss.weld.Container;
import org.jboss.weld.bootstrap.api.ServiceRegistry;
import org.jboss.weld.context.RequestContext;
import org.jboss.weld.context.unbound.UnboundLiteral;
import org.jboss.weld.events.WeldNotificationOptions;
import org.jboss.weld.events.WeldNotificationOptions.NotificationMode;
import org.jboss.weld.injection.ThreadLocalStack.ThreadLocalStackReference;
import org.jboss.weld.logging.EventLogger;
import org.jboss.weld.logging.UtilLogger;
import org.jboss.weld.manager.api.ExecutorServices;
import org.jboss.weld.resolution.QualifierInstance;
import org.jboss.weld.resolution.Resolvable;
import org.jboss.weld.resolution.ResolvableBuilder;
import org.jboss.weld.resolution.TypeSafeObserverResolver;
import org.jboss.weld.resources.SharedObjectCache;
import org.jboss.weld.security.spi.SecurityServices;
import org.jboss.weld.util.LazyValueHolder;
import org.jboss.weld.util.Observers;
import org.jboss.weld.util.Types;
import org.jboss.weld.util.cache.ComputingCache;
import org.jboss.weld.util.cache.ComputingCacheBuilder;
import org.jboss.weld.util.reflection.Reflections;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/**
 * Provides event-related operations such as observer method resolution and event delivery.
 *
 * An ObserverNotifier may be created with strict checks enabled. In such case event type checks are performed. Otherwise, the ObserverNotifier is called
 * lenient. The lenient version should be used for internal dispatching of events only.
 *
 * @author Jozef Hartinger
 * @author David Allen
 *
 */
public class ObserverNotifier {

    private static final RuntimeException NO_EXCEPTION_MARKER = new RuntimeException();

    private final TypeSafeObserverResolver resolver;
    private final SharedObjectCache sharedObjectCache;
    private final boolean strict;
    protected final CurrentEventMetadata currentEventMetadata;
    private final ComputingCache eventTypeCheckCache;
    private final Executor asyncEventExecutor;
    private final ScheduledExecutorService timerExecutor;
    private final SecurityServices securityServices;
    private final LazyValueHolder requestContextHolder;

    protected ObserverNotifier(String contextId, TypeSafeObserverResolver resolver, ServiceRegistry services, boolean strict) {
        this.resolver = resolver;
        this.sharedObjectCache = services.get(SharedObjectCache.class);
        this.strict = strict;
        this.currentEventMetadata = services.get(CurrentEventMetadata.class);
        if (strict) {
            this.eventTypeCheckCache = ComputingCacheBuilder.newBuilder().build(new EventTypeCheck());
        } else {
            // not necessary
            this.eventTypeCheckCache = null;
        }
        // fall back to FJP.commonPool() if ExecutorServices are not installed
        this.asyncEventExecutor = services.getOptional(ExecutorServices.class).map((e) -> e.getTaskExecutor()).orElse(ForkJoinPool.commonPool());
        // ScheduledExecutor might have null value
        this.timerExecutor = services.getOptional(ExecutorServices.class).map((e) -> e.getTimerExecutor()).orElse(null);
        this.securityServices = services.getRequired(SecurityServices.class);
        // LazyValueHolder is used because contexts are not ready yet at the point when ObserverNotifier is first initialized
        this.requestContextHolder = LazyValueHolder
                .forSupplier(() -> Container.instance(contextId).deploymentManager().instance().select(RequestContext.class, UnboundLiteral.INSTANCE).get());
    }

    /**
     * Resolves observer methods based on the given event type and qualifiers. If strict checks are enabled the given type is verified.
     *
     * @param event the event object
     * @param qualifiers given event qualifiers
     * @return resolved observer methods
     */
    public  ResolvedObservers resolveObserverMethods(Type eventType, Annotation... qualifiers) {
        checkEventObjectType(eventType);
        return this. resolveObserverMethods(buildEventResolvable(eventType, qualifiers));
    }

    /**
     * Resolves observer methods based on the given event type and qualifiers. If strict checks are enabled the given type is verified.
     *
     * @param event the event object
     * @param qualifiers given event qualifiers
     * @return resolved observer methods
     */
    public  ResolvedObservers resolveObserverMethods(Type eventType, Set qualifiers) {
        checkEventObjectType(eventType);
        return this. resolveObserverMethods(buildEventResolvable(eventType, qualifiers));
    }

    /**
     * Resolves observer methods using the given resolvable.
     *
     * @param resolvable the given resolvable
     * @return resolved observer methods
     */
    public  ResolvedObservers resolveObserverMethods(Resolvable resolvable) {
        return cast(resolver.resolve(resolvable, true));
    }

    /**
     * Delivers the given event object to observer methods resolved based on the runtime type of the event object and given event qualifiers. If strict checks
     * are enabled the event object type is verified.
     *
     * @param event the event object
     * @param metadata event metadata
     * @param qualifiers event qualifiers
     */
    public void fireEvent(Object event, EventMetadata metadata, Annotation... qualifiers) {
        fireEvent(event.getClass(), event, metadata, qualifiers);
    }

    /**
     * Delivers the given event object to observer methods resolved based on the given event type and qualifiers. If strict checks are enabled the given type is
     * verified.
     *
     * @param eventType the given event type
     * @param event the given event object
     * @param qualifiers event qualifiers
     */
    public void fireEvent(Type eventType, Object event, Annotation... qualifiers) {
        fireEvent(eventType, event, null, qualifiers);
    }

    public void fireEvent(Type eventType, Object event, EventMetadata metadata, Annotation... qualifiers) {
        checkEventObjectType(eventType);
        // we use the array of qualifiers for resolution so that we can catch duplicate qualifiers
        notify(resolveObserverMethods(buildEventResolvable(eventType, qualifiers)), event, metadata);
    }

    /**
     * Delivers the given event object to observer methods resolved based on the given resolvable. If strict checks are enabled the event object type is
     * verified.
     *
     * @param event the given event object
     * @param resolvable
     */
    public void fireEvent(Object event, Resolvable resolvable) {
        checkEventObjectType(event);
        notify(resolveObserverMethods(resolvable), event, null);
    }

    protected Resolvable buildEventResolvable(Type eventType, Set qualifiers) {
        // We can always cache as this is only ever called by Weld where we avoid non-static inner classes for annotation literals
        Set typeClosure = sharedObjectCache.getTypeClosureHolder(eventType).get();
        return new ResolvableBuilder(resolver.getMetaAnnotationStore()).addTypes(typeClosure).addType(Object.class).addQualifiers(qualifiers)
                .addQualifierUnchecked(QualifierInstance.ANY).create();
    }

    protected Resolvable buildEventResolvable(Type eventType, Annotation... qualifiers) {
        // We can always cache as this is only ever called by Weld where we avoid non-static inner classes for annotation literals
        return new ResolvableBuilder(resolver.getMetaAnnotationStore()).addTypes(sharedObjectCache.getTypeClosureHolder(eventType).get()).addType(Object.class)
                .addQualifiers(qualifiers).addQualifierUnchecked(QualifierInstance.ANY).create();
    }

    /**
     * Clears cached observer method resolutions and event type checks.
     */
    public void clear() {
        resolver.clear();
        if (eventTypeCheckCache != null) {
            eventTypeCheckCache.clear();
        }
    }

    protected void checkEventObjectType(Object event) {
        checkEventObjectType(event.getClass());
    }

    /**
     * If strict checks are enabled this method performs event type checks on the given type. More specifically it verifies that no type variables nor wildcards
     * are present within the event type. In addition, this method verifies, that the event type is not assignable to a container lifecycle event type. If
     * strict checks are not enabled then this method does not perform any action.
     *
     * @param eventType the given event type
     * @throws org.jboss.weld.exceptions.IllegalArgumentException if the strict mode is enabled and the event type contains a type variable, wildcard or is
     *         assignable to a container lifecycle event type
     */
    public void checkEventObjectType(Type eventType) {
        if (strict) {
            RuntimeException exception = eventTypeCheckCache.getValue(eventType);
            if (exception != NO_EXCEPTION_MARKER) {
                throw exception;
            }
        }
    }

    private static class EventTypeCheck implements Function {

        @Override
        public RuntimeException apply(Type eventType) {
            Type resolvedType = Types.getCanonicalType(eventType);
            /*
             * If the runtime type of the event object contains a type variable, the container must throw an IllegalArgumentException.
             */
            if (Types.containsTypeVariable(resolvedType)) {
                return UtilLogger.LOG.typeParameterNotAllowedInEventType(eventType);
            }
            /*
             * If the runtime type of the event object is assignable to the type of a container lifecycle event, IllegalArgumentException is thrown.
             */
            Class resolvedClass = Reflections.getRawType(eventType);
            for (Class containerEventType : Observers.CONTAINER_LIFECYCLE_EVENT_CANONICAL_SUPERTYPES) {
                if (containerEventType.isAssignableFrom(resolvedClass)) {
                    return UtilLogger.LOG.eventTypeNotAllowed(eventType);
                }
            }
            return NO_EXCEPTION_MARKER;
        }
    }

    /**
     * Delivers the given synchronous event object to synchronous and transactional observer methods. Event metadata is made available for injection into
     * observer methods, if needed. Asynchronous observer methods are ignored.
     *
     * @param observers the given observer methods
     * @param event the given event object
     * @param metadata event metadata
     */
    public  void notify(ResolvedObservers observers, T event, EventMetadata metadata) {
        if (!observers.isMetadataRequired()) {
            metadata = null;
        }
        notifySyncObservers(observers.getImmediateSyncObservers(), event, metadata, ObserverExceptionHandler.IMMEDIATE_HANDLER);
        notifyTransactionObservers(observers.getTransactionObservers(), event, metadata, ObserverExceptionHandler.IMMEDIATE_HANDLER);
    }

    protected  void notifySyncObservers(List> observers, T event, EventMetadata metadata, ObserverExceptionHandler handler) {
        if (observers.isEmpty()) {
            return;
        }
        final ThreadLocalStackReference stack = currentEventMetadata.pushIfNotNull(metadata);
        try {
            for (ObserverMethod observer : observers) {
                try {
                    Observers.notify(observer, event, metadata);
                } catch (Throwable throwable) {
                    handler.handle(throwable);
                }
            }
        } finally {
            stack.pop();
        }
    }

    protected  void notifyTransactionObservers(List> observers, T event, EventMetadata metadata,
            ObserverExceptionHandler handler) {
        notifySyncObservers(observers, event, metadata, ObserverExceptionHandler.IMMEDIATE_HANDLER); // no transaction support
    }

    /**
     * Delivers the given asynchronous event object to given observer asynchronous observer methods.
     *
     * Asynchronous observer methods are scheduled to be notified in a separate thread. Note that this method exits just after event delivery to asynchronous
     * observer methods is scheduled. {@link EventMetadata} is made available for injection into observer methods, if needed.
     *
     * Note that if any of the observer methods throws an exception, it is never thrown out of this method. Instead, all the exceptions are grouped together
     * using {@link CompletionException} and the returned {@link CompletionStage} fails with this compound exception.
     *
     * If an executor is provided then observer methods are notified using this executor. Otherwise, Weld's task executor is used.
     *
     * @param observers the given observer methods
     * @param event the given event object
     * @param metadata event metadata
     * @param options
     */
    public  CompletionStage notifyAsync(ResolvedObservers observers, U event, EventMetadata metadata, NotificationOptions options) {
        if (!observers.isMetadataRequired()) {
            metadata = null;
        }
        return notifyAsyncObservers(observers.getAsyncObservers(), event, metadata, options.getExecutor(), options);
    }

    protected  CompletionStage notifyAsyncObservers(List> observers, U event, EventMetadata metadata,
            Executor executor, NotificationOptions options) {
        if (executor == null) {
            executor = asyncEventExecutor;
        }
        if (observers.isEmpty()) {
            return AsyncEventDeliveryStage.completed(event, executor);
        }
        // We should always initialize and validate all notification options first
        final NotificationMode mode = initModeOption(options.get(WeldNotificationOptions.MODE));
        final Long timeout = initTimeoutOption(options.get(WeldNotificationOptions.TIMEOUT));
        final Consumer securityContextActionConsumer = securityServices.getSecurityContextAssociator();
        final ObserverExceptionHandler exceptionHandler;
        CompletableFuture completableFuture;

        if (observers.size() > 1 && NotificationMode.PARALLEL.equals(mode)) {
            // Attempt to notify async observers in parallel
            exceptionHandler = new CollectingExceptionHandler(new CopyOnWriteArrayList<>());
            List> completableFutures = new ArrayList<>(observers.size());
            for (ObserverMethod observer : observers) {
                completableFutures.add(CompletableFuture.supplyAsync(createSupplier(securityContextActionConsumer, event, metadata, exceptionHandler, false, () -> {
                    notifyAsyncObserver(observer, event, metadata, exceptionHandler);
                }), executor));
            }
            completableFuture = CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[] {})).thenApply((ignoredVoid) -> {
                handleExceptions(exceptionHandler);
                return event;
            });
        } else {
            // Async observers are notified serially in a single worker thread
            exceptionHandler = new CollectingExceptionHandler();
            completableFuture = CompletableFuture.supplyAsync(createSupplier(securityContextActionConsumer, event, metadata, exceptionHandler, true, () -> {
                for (ObserverMethod observer : observers) {
                    notifyAsyncObserver(observer, event, metadata, exceptionHandler);
                }
            }), executor);
        }

        // If NotificationOptionKeys.TIMEOUT is set, we will trigger the counter and use CompletableFuture.anyOf()
        if (timeout != null) {
            completableFuture = CompletableFuture.anyOf(completableFuture, startTimer(timeout)).thenApply((ignoredObject) -> event);
        }
        return new AsyncEventDeliveryStage<>(completableFuture, executor);
    }

    /**
     * Verifies that, if timeout options was set, the executor is available and input value for timeout can be interpreted as Long.
     * Returns the timeout value if all is alright, null if this option was not requested.
     */
    private Long initTimeoutOption(Object timeoutOptionValue) {
        if (timeoutOptionValue != null) {
            // throw exception if we don't have scheduled executor available
            if (timerExecutor == null) {
                throw EventLogger.LOG.noScheduledExecutorServicesProvided();
            }
            // now verify that the input value makes sense
            try {
                String inputValue = timeoutOptionValue.toString();
                return Long.parseLong(inputValue);
            } catch (NumberFormatException nfe) {
                throw EventLogger.LOG.invalidInputValueForTimeout(nfe);
            }
        } else {
            return null;
        }
    }

    private NotificationMode initModeOption(Object value) {
        if (value != null) {
            NotificationMode mode = NotificationMode.of(value);
            if (mode == null) {
                throw EventLogger.LOG.invalidNotificationMode(value);
            }
            return mode;
        }
        return null;
    }

    /**
     * Starts the timer thread and returns it as CompletableFuture.
     */
    private  CompletableFuture startTimer(Long timeout) {
        CompletableFuture timeoutFuture = new CompletableFuture();
        timerExecutor.schedule(() -> timeoutFuture.completeExceptionally(new TimeoutException()), timeout, TimeUnit.MILLISECONDS);
        return timeoutFuture;
    }

    private  void notifyAsyncObserver(ObserverMethod observer, U event, EventMetadata metadata,
            ObserverExceptionHandler exceptionHandler) {
        try {
            Observers.notify(observer, event, metadata);
        } catch (Throwable e) {
            exceptionHandler.handle(e);
        }
    }

    /**
     * The supplier associates the security context with the current thread, activates the request context, runs the "notify" action and handles exceptions if
     * required.
     *
     * @param event
     * @param metadata
     * @param exceptionHandler
     * @param handleExceptions
     * @param notifyAction
     * @return a new supplier
     */
    private  Supplier createSupplier(Consumer securityContextActionConsumer, U event, EventMetadata metadata, ObserverExceptionHandler exceptionHandler,
            boolean handleExceptions, Runnable notifyAction) {
        return () -> {
            final ThreadLocalStackReference stack = currentEventMetadata.pushIfNotNull(metadata);
            final RequestContext requestContext = requestContextHolder.get();
            securityContextActionConsumer.accept(() -> {
                try {
                    requestContext.activate();
                    notifyAction.run();
                } finally {
                    stack.pop();
                    requestContext.invalidate();
                    requestContext.deactivate();
                }
            });
            if (handleExceptions) {
                handleExceptions(exceptionHandler);
            }
            return event;
        };
    }

    @SuppressFBWarnings(value = "NP_NONNULL_PARAM_VIOLATION", justification = "https://github.com/findbugsproject/findbugs/issues/79")
    private void handleExceptions(ObserverExceptionHandler handler) {
        List handledExceptions = handler.getHandledExceptions();
        if (!handledExceptions.isEmpty()) {
            CompletionException exception = null;
            if (handledExceptions.size() == 1) {
                exception = new CompletionException(handledExceptions.get(0));
            } else {
                exception = new CompletionException(null);
            }
            for (Throwable handledException : handledExceptions) {
                exception.addSuppressed(handledException);
            }
            throw exception;
        }
    }

    /**
     * There are two different strategies of exception handling for observer methods. When an exception is raised by a synchronous or transactional observer for
     * a synchronous event, this exception stops the notification chain and the exception is propagated immediately. On the other hand, an exception thrown
     * during asynchronous event delivery never is never propagated directly. Instead, all the exceptions for a given asynchronous event are collected and then
     * made available together using CompletionException.
     *
     * @author Jozef Hartinger
     *
     */
    protected interface ObserverExceptionHandler {

        ObserverExceptionHandler IMMEDIATE_HANDLER = throwable -> {
            if (throwable instanceof RuntimeException) {
                throw (RuntimeException) throwable;
            }
            if (throwable instanceof Error) {
                throw (Error) throwable;
            }
            throw new ObserverException(throwable);
        };

        void handle(Throwable throwable);

        default List getHandledExceptions() {
            return Collections.emptyList();
        }
    }

    static class CollectingExceptionHandler implements ObserverExceptionHandler {

        private List throwables;

        CollectingExceptionHandler() {
            this(new LinkedList<>());
        }

        CollectingExceptionHandler(List throwables) {
            this.throwables = throwables;
        }

        @Override
        public void handle(Throwable throwable) {
            throwables.add(throwable);
        }

        @Override
        public List getHandledExceptions() {
            return throwables;
        }
    }
}