/plugin/src/main/java/com/atlassian/confluence/extra/userlister/UserListener.java
https://bitbucket.org/atlassian/confluence-user-lister-plugin · Java · 91 lines · 61 code · 12 blank · 18 comment · 0 complexity · f506efb4cd840ec6d079e33d2147b4c3 MD5 · raw file
- package com.atlassian.confluence.extra.userlister;
- import com.atlassian.confluence.event.events.security.LoginEvent;
- import com.atlassian.confluence.event.events.security.LogoutEvent;
- import com.atlassian.event.api.AsynchronousPreferred;
- import com.atlassian.event.api.EventListener;
- import com.atlassian.event.api.EventListenerRegistrar;
- import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
- import org.springframework.beans.factory.DisposableBean;
- import org.springframework.beans.factory.InitializingBean;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Component;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.LinkedBlockingQueue;
- import java.util.concurrent.ThreadPoolExecutor;
- import java.util.concurrent.ThreadPoolExecutor.DiscardOldestPolicy;
- import static io.atlassian.util.concurrent.ThreadFactories.Type.DAEMON;
- import static io.atlassian.util.concurrent.ThreadFactories.namedThreadFactory;
- import static java.util.concurrent.TimeUnit.MILLISECONDS;
- /**
- * A component which listens for {@link LoginEvent}s and {@link LogoutEvent}s published by Confluence, and updates
- * the record of currently logged-in users in a {@link UserListManager}.
- * <p>
- * Note that {@link LoginEvent} and {@link LogoutEvent} are both synchronous events, i.e. are delivered to this component
- * on the same thread that publishes them. This is important because {@link DefaultUserListManager} uses a Hazelcast data
- * structure to store the session records, and we don't want to block the HTTP thread by talking to another cluster node.
- * <p>
- * To avoid blocking this thread, we use an internal {@link ExecutorService} to asynchronously process the events, since
- * eventual consistency is fine. Ideally, the events would be {@link AsynchronousPreferred}, avoiding the need to do this.
- * <p>
- * Note also that the executor is configured defensively, with a single background thread and a fixed size queue. If events
- * are generated faster than we can process them, then it will start discarding the oldest events in the queue.
- */
- @Component
- public class UserListener implements InitializingBean, DisposableBean {
- private final EventListenerRegistrar eventListenerRegistrar;
- private final UserListManager userListManager;
- private final ExecutorService eventListenerExecutor;
- @Autowired
- public UserListener(final @ComponentImport EventListenerRegistrar eventListenerRegistrar,
- final UserListManager userListManager) {
- this(eventListenerRegistrar, userListManager, createEventListenerExecutor(1000, 1));
- }
- UserListener(final EventListenerRegistrar eventListenerRegistrar,
- final UserListManager userListManager,
- final ExecutorService eventListenerExecutor) {
- this.eventListenerRegistrar = eventListenerRegistrar;
- this.userListManager = userListManager;
- this.eventListenerExecutor = eventListenerExecutor;
- }
- @EventListener
- public void handleLoginEvent(final LoginEvent loginEvent) {
- eventListenerExecutor.submit(() -> userListManager.registerLoggedInUser(loginEvent.getUsername(), loginEvent.getSessionId()));
- }
- @EventListener
- public void handleLogoutEvent(final LogoutEvent logoutEvent) {
- eventListenerExecutor.submit(() -> userListManager.unregisterLoggedInUser(logoutEvent.getUsername(), logoutEvent.getSessionId()));
- }
- @Override
- public void afterPropertiesSet() {
- eventListenerRegistrar.register(this);
- }
- @Override
- public void destroy() {
- eventListenerExecutor.shutdownNow();
- eventListenerRegistrar.unregister(this);
- }
- /**
- * Creates an executor with a queue of the given capacity and number of threads, and which
- * uses a {@link DiscardOldestPolicy} when the queue fills up.
- */
- private static ExecutorService createEventListenerExecutor(final int capacity, final int poolSize) {
- return new ThreadPoolExecutor(
- poolSize, poolSize,
- 0L, MILLISECONDS,
- new LinkedBlockingQueue<>(capacity),
- namedThreadFactory(UserListener.class.getName(), DAEMON),
- new DiscardOldestPolicy());
- }
- }