PageRenderTime 53ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/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
  1. package com.atlassian.confluence.extra.userlister;
  2. import com.atlassian.confluence.event.events.security.LoginEvent;
  3. import com.atlassian.confluence.event.events.security.LogoutEvent;
  4. import com.atlassian.event.api.AsynchronousPreferred;
  5. import com.atlassian.event.api.EventListener;
  6. import com.atlassian.event.api.EventListenerRegistrar;
  7. import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
  8. import org.springframework.beans.factory.DisposableBean;
  9. import org.springframework.beans.factory.InitializingBean;
  10. import org.springframework.beans.factory.annotation.Autowired;
  11. import org.springframework.stereotype.Component;
  12. import java.util.concurrent.ExecutorService;
  13. import java.util.concurrent.LinkedBlockingQueue;
  14. import java.util.concurrent.ThreadPoolExecutor;
  15. import java.util.concurrent.ThreadPoolExecutor.DiscardOldestPolicy;
  16. import static io.atlassian.util.concurrent.ThreadFactories.Type.DAEMON;
  17. import static io.atlassian.util.concurrent.ThreadFactories.namedThreadFactory;
  18. import static java.util.concurrent.TimeUnit.MILLISECONDS;
  19. /**
  20. * A component which listens for {@link LoginEvent}s and {@link LogoutEvent}s published by Confluence, and updates
  21. * the record of currently logged-in users in a {@link UserListManager}.
  22. * <p>
  23. * Note that {@link LoginEvent} and {@link LogoutEvent} are both synchronous events, i.e. are delivered to this component
  24. * on the same thread that publishes them. This is important because {@link DefaultUserListManager} uses a Hazelcast data
  25. * structure to store the session records, and we don't want to block the HTTP thread by talking to another cluster node.
  26. * <p>
  27. * To avoid blocking this thread, we use an internal {@link ExecutorService} to asynchronously process the events, since
  28. * eventual consistency is fine. Ideally, the events would be {@link AsynchronousPreferred}, avoiding the need to do this.
  29. * <p>
  30. * Note also that the executor is configured defensively, with a single background thread and a fixed size queue. If events
  31. * are generated faster than we can process them, then it will start discarding the oldest events in the queue.
  32. */
  33. @Component
  34. public class UserListener implements InitializingBean, DisposableBean {
  35. private final EventListenerRegistrar eventListenerRegistrar;
  36. private final UserListManager userListManager;
  37. private final ExecutorService eventListenerExecutor;
  38. @Autowired
  39. public UserListener(final @ComponentImport EventListenerRegistrar eventListenerRegistrar,
  40. final UserListManager userListManager) {
  41. this(eventListenerRegistrar, userListManager, createEventListenerExecutor(1000, 1));
  42. }
  43. UserListener(final EventListenerRegistrar eventListenerRegistrar,
  44. final UserListManager userListManager,
  45. final ExecutorService eventListenerExecutor) {
  46. this.eventListenerRegistrar = eventListenerRegistrar;
  47. this.userListManager = userListManager;
  48. this.eventListenerExecutor = eventListenerExecutor;
  49. }
  50. @EventListener
  51. public void handleLoginEvent(final LoginEvent loginEvent) {
  52. eventListenerExecutor.submit(() -> userListManager.registerLoggedInUser(loginEvent.getUsername(), loginEvent.getSessionId()));
  53. }
  54. @EventListener
  55. public void handleLogoutEvent(final LogoutEvent logoutEvent) {
  56. eventListenerExecutor.submit(() -> userListManager.unregisterLoggedInUser(logoutEvent.getUsername(), logoutEvent.getSessionId()));
  57. }
  58. @Override
  59. public void afterPropertiesSet() {
  60. eventListenerRegistrar.register(this);
  61. }
  62. @Override
  63. public void destroy() {
  64. eventListenerExecutor.shutdownNow();
  65. eventListenerRegistrar.unregister(this);
  66. }
  67. /**
  68. * Creates an executor with a queue of the given capacity and number of threads, and which
  69. * uses a {@link DiscardOldestPolicy} when the queue fills up.
  70. */
  71. private static ExecutorService createEventListenerExecutor(final int capacity, final int poolSize) {
  72. return new ThreadPoolExecutor(
  73. poolSize, poolSize,
  74. 0L, MILLISECONDS,
  75. new LinkedBlockingQueue<>(capacity),
  76. namedThreadFactory(UserListener.class.getName(), DAEMON),
  77. new DiscardOldestPolicy());
  78. }
  79. }