This is an automated email from the ASF dual-hosted git repository.

tbonelee pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zeppelin.git


The following commit(s) were added to refs/heads/master by this push:
     new 796c8c094f [ZEPPELIN-6340] Add ZeppelinEventBus and update 
NotebookServer to handle NoteRemoveEvent
796c8c094f is described below

commit 796c8c094f0324a12495a133deec255ea25be7ac
Author: SeungYoung Oh <[email protected]>
AuthorDate: Sat Oct 17 02:26:57 2026 +0900

    [ZEPPELIN-6340] Add ZeppelinEventBus and update NotebookServer to handle 
NoteRemoveEvent
    
    ### What is this PR for?
    
    This PR is the first step toward migrating Zeppelin’s listener-based event 
handling to an EventBus model, as outlined in the 
[proposal](https://cwiki.apache.org/confluence/x/vAsGFw). It sets up the 
EventBus infrastructure and migrates `NoteRemoveEvent` handling to it.
    
    Key changes:
    
    * Added the `ZeppelinEventBus` class
    * Added RxJava 3 as a dependency of the `zeppelin-zengine` module
    * Added the `zeppelin.eventbus.enabled` feature flag
    * Updated `NotebookServer` to handle `NoteRemoveEvent` through 
`ZeppelinEventBus` when the feature flag is enabled
    
    This PR supersedes #5085. The original branch had fallen behind the current 
codebase, and the implementation has since changed, so I opened a new PR.
    ### What type of PR is it?
    Improvement
    
    ### Todos
    * [ ] Refactor other NoteEventListener methods and additional listeners to 
EventBus
    
    ### What is the Jira issue?
    * Open an issue on Jira https://issues.apache.org/jira/browse/ZEPPELIN-6340
    
    ### How should this be tested?
    - Run the existing test suite against the updated code
    - Verify the note deletion flow through the UI
    
    ### Screenshots (if appropriate)
    
    ### Questions:
    * Does the license files need to update? N
    * Is there breaking changes for older versions? N
    * Does this needs documentation? N
    
    Closes #5441 from seung-00/ZEPPELIN-6340.
    
    Signed-off-by: ChanHo Lee <[email protected]>
---
 conf/zeppelin-site.xml.template                    |  6 ++
 zeppelin-server/pom.xml                            |  6 ++
 .../zeppelin/conf/ZeppelinConfiguration.java       |  5 +-
 .../org/apache/zeppelin/eventbus/EventBus.java     | 34 +++++++++
 .../org/apache/zeppelin/eventbus/NoOpEventBus.java | 47 +++++++++++++
 .../org/apache/zeppelin/eventbus/NoteEvent.java    | 28 ++++++++
 .../apache/zeppelin/eventbus/NoteRemovedEvent.java | 43 ++++++++++++
 .../apache/zeppelin/eventbus/ZeppelinEvent.java    | 21 ++++++
 .../apache/zeppelin/eventbus/ZeppelinEventBus.java | 63 +++++++++++++++++
 .../org/apache/zeppelin/notebook/Notebook.java     | 23 ++++--
 .../org/apache/zeppelin/server/ZeppelinServer.java |  9 +++
 .../org/apache/zeppelin/socket/NotebookServer.java | 59 ++++++++++++----
 .../zeppelin/eventbus/ZeppelinEventBusTest.java    | 82 ++++++++++++++++++++++
 .../helium/HeliumApplicationFactoryTest.java       |  3 +-
 .../interpreter/AbstractInterpreterTest.java       |  6 +-
 .../org/apache/zeppelin/notebook/NotebookTest.java |  4 +-
 .../notebook/repo/NotebookRepoSyncTest.java        |  6 +-
 .../zeppelin/service/NotebookServiceTest.java      |  8 +--
 .../apache/zeppelin/socket/NotebookServerTest.java | 30 ++++++++
 19 files changed, 457 insertions(+), 26 deletions(-)

diff --git a/conf/zeppelin-site.xml.template b/conf/zeppelin-site.xml.template
index 6cf0e071d6..f30b346d66 100755
--- a/conf/zeppelin-site.xml.template
+++ b/conf/zeppelin-site.xml.template
@@ -872,4 +872,10 @@
   <description>Directory for assistant conversations.</description>
 </property>
 
+<property>
+  <name>zeppelin.eventbus.enabled</name>
+  <value>false</value>
+  <description>Enables the new event-driven architecture using an in-process 
EventBus</description>
+</property>
+
 </configuration>
diff --git a/zeppelin-server/pom.xml b/zeppelin-server/pom.xml
index 7191c0bb01..12699c9ce3 100644
--- a/zeppelin-server/pom.xml
+++ b/zeppelin-server/pom.xml
@@ -97,6 +97,12 @@
       <artifactId>jakarta.inject-api</artifactId>
     </dependency>
 
+    <dependency>
+      <groupId>io.reactivex.rxjava3</groupId>
+      <artifactId>rxjava</artifactId>
+      <version>3.1.12</version>
+    </dependency>
+
     <dependency>
       <groupId>commons-io</groupId>
       <artifactId>commons-io</artifactId>
diff --git 
a/zeppelin-server/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
 
b/zeppelin-server/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
index 8b161d174f..781e7d9f57 100644
--- 
a/zeppelin-server/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
+++ 
b/zeppelin-server/src/main/java/org/apache/zeppelin/conf/ZeppelinConfiguration.java
@@ -965,6 +965,8 @@ public class ZeppelinConfiguration {
     return getBoolean(ConfVars.ZEPPELIN_METRIC_ENABLE_PROMETHEUS);
   }
 
+  public boolean isEventBusEnabled() { return 
getBoolean(ConfVars.ZEPPELIN_EVENTBUS_ENABLED); }
+
   public DEFAULT_UI getDefaultUi() {
     return 
DEFAULT_UI.valueOf(getString(ConfVars.ZEPPELIN_DEFAULT_UI).toUpperCase());
   }
@@ -1222,7 +1224,8 @@ public class ZeppelinConfiguration {
     ),
     ZEPPELIN_ASSISTANT_OPENAI_MODEL(
         "zeppelin.assistant.openai.model", "gpt-6-luna"
-    );
+    ),
+    ZEPPELIN_EVENTBUS_ENABLED("zeppelin.eventbus.enabled", false);
 
     private String varName;
     private Class<?> varClass;
diff --git 
a/zeppelin-server/src/main/java/org/apache/zeppelin/eventbus/EventBus.java 
b/zeppelin-server/src/main/java/org/apache/zeppelin/eventbus/EventBus.java
new file mode 100644
index 0000000000..3be7559776
--- /dev/null
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/eventbus/EventBus.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.zeppelin.eventbus;
+
+import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.functions.Consumer;
+
+public interface EventBus {
+
+  void post(ZeppelinEvent event);
+
+  /**
+   * Handlers may not run on the same thread that calls post().
+   */
+  <T extends ZeppelinEvent> Disposable subscribe(
+      Class<T> eventType,
+      Consumer<? super T> handler
+  );
+}
diff --git 
a/zeppelin-server/src/main/java/org/apache/zeppelin/eventbus/NoOpEventBus.java 
b/zeppelin-server/src/main/java/org/apache/zeppelin/eventbus/NoOpEventBus.java
new file mode 100644
index 0000000000..fe6198790b
--- /dev/null
+++ 
b/zeppelin-server/src/main/java/org/apache/zeppelin/eventbus/NoOpEventBus.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.zeppelin.eventbus;
+
+import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.functions.Consumer;
+import jakarta.inject.Inject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class NoOpEventBus implements EventBus {
+
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(NoOpEventBus.class);
+
+  @Inject
+  public NoOpEventBus() {
+    LOGGER.info("Starting NoOpEventBus");
+  }
+
+  @Override
+  public void post(ZeppelinEvent event) {
+    LOGGER.debug("Posting event: {}", event.getClass().getName());
+  }
+
+  @Override
+  public <T extends ZeppelinEvent> Disposable subscribe(
+      Class<T> eventType,
+      Consumer<? super T> handler
+  ) {
+    return Disposable.disposed();
+  }
+}
diff --git 
a/zeppelin-server/src/main/java/org/apache/zeppelin/eventbus/NoteEvent.java 
b/zeppelin-server/src/main/java/org/apache/zeppelin/eventbus/NoteEvent.java
new file mode 100644
index 0000000000..2b6b806a14
--- /dev/null
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/eventbus/NoteEvent.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.zeppelin.eventbus;
+
+import org.apache.zeppelin.notebook.Note;
+import org.apache.zeppelin.user.AuthenticationInfo;
+
+public interface NoteEvent extends ZeppelinEvent {
+
+  Note getNote();
+
+  AuthenticationInfo getSubject();
+}
diff --git 
a/zeppelin-server/src/main/java/org/apache/zeppelin/eventbus/NoteRemovedEvent.java
 
b/zeppelin-server/src/main/java/org/apache/zeppelin/eventbus/NoteRemovedEvent.java
new file mode 100644
index 0000000000..de7ca88749
--- /dev/null
+++ 
b/zeppelin-server/src/main/java/org/apache/zeppelin/eventbus/NoteRemovedEvent.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.zeppelin.eventbus;
+
+import org.apache.zeppelin.notebook.Note;
+import org.apache.zeppelin.user.AuthenticationInfo;
+
+public class NoteRemovedEvent implements NoteEvent {
+
+  private final Note note;
+
+  private final AuthenticationInfo subject;
+
+  public NoteRemovedEvent(Note note, AuthenticationInfo subject) {
+    this.note = note;
+    this.subject = subject;
+  }
+
+  @Override
+  public Note getNote() {
+    return this.note;
+  }
+
+  @Override
+  public AuthenticationInfo getSubject() {
+    return subject;
+  }
+}
diff --git 
a/zeppelin-server/src/main/java/org/apache/zeppelin/eventbus/ZeppelinEvent.java 
b/zeppelin-server/src/main/java/org/apache/zeppelin/eventbus/ZeppelinEvent.java
new file mode 100644
index 0000000000..2f181ef7e8
--- /dev/null
+++ 
b/zeppelin-server/src/main/java/org/apache/zeppelin/eventbus/ZeppelinEvent.java
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.zeppelin.eventbus;
+
+public interface ZeppelinEvent {
+}
diff --git 
a/zeppelin-server/src/main/java/org/apache/zeppelin/eventbus/ZeppelinEventBus.java
 
b/zeppelin-server/src/main/java/org/apache/zeppelin/eventbus/ZeppelinEventBus.java
new file mode 100644
index 0000000000..1ae66db7a0
--- /dev/null
+++ 
b/zeppelin-server/src/main/java/org/apache/zeppelin/eventbus/ZeppelinEventBus.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.zeppelin.eventbus;
+
+import io.reactivex.rxjava3.disposables.Disposable;
+import io.reactivex.rxjava3.functions.Consumer;
+import io.reactivex.rxjava3.subjects.PublishSubject;
+import io.reactivex.rxjava3.subjects.Subject;
+import jakarta.inject.Inject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ZeppelinEventBus implements EventBus {
+
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(ZeppelinEventBus.class);
+
+  private final Subject<ZeppelinEvent> eventBus;
+
+  @Inject
+  public ZeppelinEventBus() {
+    LOGGER.info("Starting ZeppelinEventBus");
+
+    eventBus = PublishSubject.<ZeppelinEvent>create().toSerialized();
+  }
+
+  @Override
+  public void post(ZeppelinEvent event) {
+    LOGGER.debug("Posting event: {}", event.getClass().getName());
+
+    eventBus.onNext(event);
+  }
+
+  @Override
+  public <T extends ZeppelinEvent> Disposable subscribe(
+      Class<T> eventType,
+      Consumer<? super T> handler
+  ) {
+    return eventBus.ofType(eventType).subscribe(event -> {
+      try {
+        handler.accept(event);
+      } catch (Exception e) {
+        LOGGER.error("Failed to handle event: {}", eventType.getName(), e);
+      }
+    }, error -> LOGGER.error(
+        "Event subscription terminated: {}", eventType.getName(), error)
+    );
+  }
+}
diff --git 
a/zeppelin-server/src/main/java/org/apache/zeppelin/notebook/Notebook.java 
b/zeppelin-server/src/main/java/org/apache/zeppelin/notebook/Notebook.java
index 10c2abca77..9805300ab2 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/notebook/Notebook.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/notebook/Notebook.java
@@ -44,6 +44,8 @@ import org.apache.zeppelin.conf.ZeppelinConfiguration;
 import org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars;
 import org.apache.zeppelin.display.AngularObject;
 import org.apache.zeppelin.display.AngularObjectRegistry;
+import org.apache.zeppelin.eventbus.EventBus;
+import org.apache.zeppelin.eventbus.NoteRemovedEvent;
 import org.apache.zeppelin.interpreter.Interpreter;
 import org.apache.zeppelin.interpreter.InterpreterFactory;
 import org.apache.zeppelin.interpreter.InterpreterGroup;
@@ -86,11 +88,13 @@ public class Notebook {
   private Credentials credentials;
   private final List<Consumer<String>> initConsumers;
   private ExecutorService initExecutor;
+  private EventBus eventBus;
 
   /**
    * Main constructor \w manual Dependency Injection
    *
    * @throws IOException
+   *
    * @throws SchedulerException
    */
   public Notebook(
@@ -100,8 +104,8 @@ public class Notebook {
       NoteManager noteManager,
       InterpreterFactory replFactory,
       InterpreterSettingManager interpreterSettingManager,
-      Credentials credentials)
-      {
+      Credentials credentials,
+      EventBus eventBus) {
     this.zConf = zConf;
     this.authorizationService = authorizationService;
     this.noteManager = noteManager;
@@ -111,6 +115,7 @@ public class Notebook {
     // TODO(zjffdu) cycle refer, not a good solution
     this.interpreterSettingManager.setNotebook(this);
     this.credentials = credentials;
+    this.eventBus = eventBus;
     addNotebookEventListener(this.interpreterSettingManager);
     initConsumers = new LinkedList<>();
   }
@@ -221,7 +226,8 @@ public class Notebook {
       InterpreterFactory replFactory,
       InterpreterSettingManager interpreterSettingManager,
       Credentials credentials,
-      NoteEventListener noteEventListener)
+      NoteEventListener noteEventListener,
+      EventBus eventBus)
       throws IOException {
     this(
         zConf,
@@ -230,7 +236,8 @@ public class Notebook {
         noteManager,
         replFactory,
         interpreterSettingManager,
-        credentials);
+        credentials,
+        eventBus);
     if (null != noteEventListener) {
       addNotebookEventListener(noteEventListener);
     }
@@ -432,7 +439,11 @@ public class Notebook {
     note.setRemoved(true);
     noteManager.removeNote(note.getId(), subject);
     authorizationService.removeNoteAuth(note.getId());
+
     fireNoteRemoveEvent(note, subject);
+    if (zConf.isEventBusEnabled()) {
+      eventBus.post(new NoteRemovedEvent(note, subject));
+    }
   }
 
   public void removeCorruptedNote(String noteId, AuthenticationInfo subject) 
throws IOException {
@@ -564,6 +575,9 @@ public class Notebook {
             note.setRemoved(true);
             authorizationService.removeNoteAuth(note.getId());
             fireNoteRemoveEvent(note, subject);
+            if (zConf.isEventBusEnabled()) {
+              eventBus.post(new NoteRemovedEvent(note, subject));
+            }
           }
           return null;
         });
@@ -843,6 +857,7 @@ public class Notebook {
     }
   }
 
+
   private void fireNoteRemoveEvent(Note note, AuthenticationInfo subject) {
     for (NoteEventListener listener : noteEventListeners) {
       listener.onNoteRemove(note, subject);
diff --git 
a/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java 
b/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java
index af255aa2c6..06924121b4 100644
--- 
a/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java
+++ 
b/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java
@@ -64,6 +64,9 @@ import org.apache.zeppelin.conf.ZeppelinConfiguration;
 import org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars;
 import org.apache.zeppelin.conf.ZeppelinConfiguration.DEFAULT_UI;
 import org.apache.zeppelin.display.AngularObjectRegistryListener;
+import org.apache.zeppelin.eventbus.EventBus;
+import org.apache.zeppelin.eventbus.NoOpEventBus;
+import org.apache.zeppelin.eventbus.ZeppelinEventBus;
 import org.apache.zeppelin.healthcheck.HealthChecks;
 import org.apache.zeppelin.helium.ApplicationEventListener;
 import org.apache.zeppelin.helium.Helium;
@@ -182,6 +185,11 @@ public class ZeppelinServer implements AutoCloseable {
             bind(storage).to(ConfigStorage.class);
             bindAsContract(PluginManager.class).in(Singleton.class);
             
bind(GsonNoteParser.class).to(NoteParser.class).in(Singleton.class);
+            if (zConf.isEventBusEnabled()) {
+              
bind(ZeppelinEventBus.class).to(EventBus.class).in(Singleton.class);
+            } else {
+              bind(NoOpEventBus.class).to(EventBus.class).in(Singleton.class);
+            }
             bindAsContract(InterpreterFactory.class).in(Singleton.class);
             
bindAsContract(NotebookRepoSync.class).to(NotebookRepo.class).in(Singleton.class);
             bindAsContract(Helium.class).in(Singleton.class);
@@ -388,6 +396,7 @@ public class ZeppelinServer implements AutoCloseable {
           if (!zConf.isRecoveryEnabled()) {
             
sharedServiceLocator.getService(InterpreterSettingManager.class).close();
           }
+          sharedServiceLocator.getService(NotebookServer.class).close();
           sharedServiceLocator.getService(Notebook.class).close();
         }
       } catch (Exception e) {
diff --git 
a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java 
b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java
index 63ce877f86..ca1c1756f1 100644
--- 
a/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java
+++ 
b/zeppelin-server/src/main/java/org/apache/zeppelin/socket/NotebookServer.java
@@ -43,6 +43,8 @@ import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
+
+import io.reactivex.rxjava3.disposables.CompositeDisposable;
 import jakarta.inject.Inject;
 import jakarta.inject.Provider;
 import jakarta.websocket.CloseReason;
@@ -66,6 +68,8 @@ import org.apache.zeppelin.display.AngularObjectRegistry;
 import org.apache.zeppelin.display.AngularObjectRegistryListener;
 import org.apache.zeppelin.display.GUI;
 import org.apache.zeppelin.display.Input;
+import org.apache.zeppelin.eventbus.EventBus;
+import org.apache.zeppelin.eventbus.NoteRemovedEvent;
 import org.apache.zeppelin.helium.ApplicationEventListener;
 import org.apache.zeppelin.helium.HeliumPackage;
 import org.apache.zeppelin.interpreter.InterpreterGroup;
@@ -123,7 +127,8 @@ public class NotebookServer implements 
AngularObjectRegistryListener,
     RemoteInterpreterProcessListener,
     ApplicationEventListener,
     ParagraphJobListener,
-    NoteEventListener {
+    NoteEventListener,
+    AutoCloseable {
 
   /**
    * Job manager service type.
@@ -170,6 +175,7 @@ public class NotebookServer implements 
AngularObjectRegistryListener,
   private AuthorizationService authorizationService;
   private Provider<JobManagerService> jobManagerServiceProvider;
   private Provider<Assistant> assistantProvider;
+  private CompositeDisposable subscriptions;
 
   public NotebookServer() {
     NotebookServer.self.set(this);
@@ -181,6 +187,26 @@ public class NotebookServer implements 
AngularObjectRegistryListener,
     this.zConf = zConf;
   }
 
+  @Inject
+  public void registerEventBus(EventBus eventBus, ZeppelinConfiguration zConf) 
{
+    if (!zConf.isEventBusEnabled()) {
+      LOGGER.debug("ZeppelinEventBus is disabled");
+      return;
+    }
+    subscriptions = new CompositeDisposable();
+
+    subscriptions.add(eventBus.subscribe(
+        NoteRemovedEvent.class, event -> handleNoteRemove(event.getNote()))
+    );
+  }
+
+  @Override
+  public void close() {
+    if (subscriptions != null && !subscriptions.isDisposed()) {
+      subscriptions.dispose();
+    }
+  }
+
   @Inject
   public void setNoteParser(Provider<NoteParser> noteParser) {
     this.noteParser = noteParser;
@@ -2080,19 +2106,12 @@ public class NotebookServer implements 
AngularObjectRegistryListener,
 
   @Override
   public void onNoteRemove(Note note, AuthenticationInfo subject) {
-    try {
-      broadcastUpdateNoteJobInfo(note, System.currentTimeMillis() - 5000);
-    } catch (IOException e) {
-      LOGGER.warn("can not broadcast for job manager: {}", e.getMessage(), e);
-    }
-
-    try {
-      getJobManagerService().removeNoteJobInfo(note.getId(), null,
-          new JobManagerServiceCallback());
-    } catch (IOException e) {
-      LOGGER.warn("can not broadcast for job manager: {}", e.getMessage(), e);
+    if (zConf.isEventBusEnabled()) {
+      LOGGER.debug("ZeppelinEventBus is enabled");
+      return;
     }
 
+    handleNoteRemove(note);
   }
 
   @Override
@@ -2549,4 +2568,20 @@ public class NotebookServer implements 
AngularObjectRegistryListener,
       }
     }
   }
+
+  private void handleNoteRemove(Note note) {
+    try {
+      broadcastUpdateNoteJobInfo(note, System.currentTimeMillis() - 5000);
+    } catch (IOException e) {
+      LOGGER.warn("can not broadcast for job manager: {}", e.getMessage(), e);
+    }
+
+    try {
+      getJobManagerService().removeNoteJobInfo(
+              note.getId(), null, new JobManagerServiceCallback()
+      );
+    } catch (IOException e) {
+      LOGGER.warn("can not broadcast for job manager: {}", e.getMessage(), e);
+    }
+  }
 }
diff --git 
a/zeppelin-server/src/test/java/org/apache/zeppelin/eventbus/ZeppelinEventBusTest.java
 
b/zeppelin-server/src/test/java/org/apache/zeppelin/eventbus/ZeppelinEventBusTest.java
new file mode 100644
index 0000000000..3f9f83aebc
--- /dev/null
+++ 
b/zeppelin-server/src/test/java/org/apache/zeppelin/eventbus/ZeppelinEventBusTest.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.zeppelin.eventbus;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import io.reactivex.rxjava3.disposables.Disposable;
+
+class ZeppelinEventBusTest {
+
+  @Test
+  void testSubscription() {
+    EventBus bus = new ZeppelinEventBus();
+    List<String> received = new ArrayList<>();
+    Disposable subscription = bus.subscribe(MockEvent.class, event -> 
received.add(event.payload));
+
+    bus.post(new ZeppelinEvent() { });
+    bus.post(new MockEvent("received"));
+    assertEquals(List.of("received"), received);
+
+    subscription.dispose();
+    bus.post(new MockEvent("ignored"));
+    assertEquals(List.of("received"), received);
+  }
+
+  @Test
+  void testContinueAfterException() {
+    EventBus bus = new ZeppelinEventBus();
+    List<String> received = new ArrayList<>();
+    Disposable subscription = bus.subscribe(MockEvent.class, event -> {
+      if ("checked".equals(event.payload)) {
+        throw new IOException("Handler failed");
+      }
+      if ("runtime".equals(event.payload)) {
+        throw new IllegalStateException("Handler failed");
+      }
+      received.add(event.payload);
+    });
+
+    try {
+      bus.post(new MockEvent("checked"));
+      bus.post(new MockEvent("after checked"));
+      bus.post(new MockEvent("runtime"));
+      bus.post(new MockEvent("after runtime"));
+
+      assertFalse(subscription.isDisposed());
+      assertEquals(List.of("after checked", "after runtime"), received);
+    } finally {
+      subscription.dispose();
+    }
+  }
+
+  private static class MockEvent implements ZeppelinEvent {
+    private final String payload;
+
+    MockEvent(String payload) {
+      this.payload = payload;
+    }
+  }
+}
diff --git 
a/zeppelin-server/src/test/java/org/apache/zeppelin/helium/HeliumApplicationFactoryTest.java
 
b/zeppelin-server/src/test/java/org/apache/zeppelin/helium/HeliumApplicationFactoryTest.java
index 2f356281ab..d66662ebfa 100644
--- 
a/zeppelin-server/src/test/java/org/apache/zeppelin/helium/HeliumApplicationFactoryTest.java
+++ 
b/zeppelin-server/src/test/java/org/apache/zeppelin/helium/HeliumApplicationFactoryTest.java
@@ -70,7 +70,8 @@ class HeliumApplicationFactoryTest extends 
AbstractInterpreterTest {
             new NoteManager(notebookRepo, ZeppelinConfiguration.load()),
             interpreterFactory,
             interpreterSettingManager,
-            new Credentials());
+            new Credentials(),
+            eventBus);
 
     heliumAppFactory = new HeliumApplicationFactory(notebook, null);
 
diff --git 
a/zeppelin-server/src/test/java/org/apache/zeppelin/interpreter/AbstractInterpreterTest.java
 
b/zeppelin-server/src/test/java/org/apache/zeppelin/interpreter/AbstractInterpreterTest.java
index 0bfd19014b..2f7b6a65cb 100644
--- 
a/zeppelin-server/src/test/java/org/apache/zeppelin/interpreter/AbstractInterpreterTest.java
+++ 
b/zeppelin-server/src/test/java/org/apache/zeppelin/interpreter/AbstractInterpreterTest.java
@@ -21,6 +21,7 @@ package org.apache.zeppelin.interpreter;
 import org.apache.commons.io.FileUtils;
 import org.apache.zeppelin.conf.ZeppelinConfiguration;
 import org.apache.zeppelin.display.AngularObjectRegistryListener;
+import org.apache.zeppelin.eventbus.ZeppelinEventBus;
 import org.apache.zeppelin.helium.ApplicationEventListener;
 import org.apache.zeppelin.interpreter.remote.RemoteInterpreterProcessListener;
 import org.apache.zeppelin.notebook.AuthorizationService;
@@ -65,6 +66,7 @@ public abstract class AbstractInterpreterTest {
   protected ZeppelinConfiguration zConf;
   protected ConfigStorage storage;
   protected PluginManager pluginManager;
+  protected ZeppelinEventBus eventBus;
 
   @BeforeEach
   public void setUp() throws Exception {
@@ -100,12 +102,12 @@ public abstract class AbstractInterpreterTest {
     
zConf.setProperty(ZeppelinConfiguration.ConfVars.ZEPPELIN_INTERPRETER_GROUP_DEFAULT.getVarName(),
         "test");
 
-
     NotebookRepo notebookRepo = new InMemoryNotebookRepo();
     NoteManager noteManager = new NoteManager(notebookRepo, zConf);
     noteParser = new GsonNoteParser(zConf);
     storage = ConfigStorage.createConfigStorage(zConf);
     pluginManager = new PluginManager(zConf);
+    eventBus = new ZeppelinEventBus();
     AuthorizationService authorizationService =
         new AuthorizationService(noteManager, zConf, storage);
 
@@ -114,7 +116,7 @@ public abstract class AbstractInterpreterTest {
         mock(ApplicationEventListener.class), storage, pluginManager);
     interpreterFactory = new InterpreterFactory(interpreterSettingManager);
     Credentials credentials = new Credentials(zConf, storage);
-    notebook = new Notebook(zConf, authorizationService, notebookRepo, 
noteManager, interpreterFactory, interpreterSettingManager, credentials);
+    notebook = new Notebook(zConf, authorizationService, notebookRepo, 
noteManager, interpreterFactory, interpreterSettingManager, credentials, 
eventBus);
     interpreterSettingManager.setNotebook(notebook);
   }
 
diff --git 
a/zeppelin-server/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java 
b/zeppelin-server/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java
index f7113944f8..dece3335a4 100644
--- 
a/zeppelin-server/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java
+++ 
b/zeppelin-server/src/test/java/org/apache/zeppelin/notebook/NotebookTest.java
@@ -21,6 +21,7 @@ import org.apache.commons.io.IOUtils;
 import org.apache.zeppelin.conf.ZeppelinConfiguration;
 import org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars;
 import org.apache.zeppelin.display.AngularObjectRegistry;
+import org.apache.zeppelin.eventbus.ZeppelinEventBus;
 import org.apache.zeppelin.interpreter.AbstractInterpreterTest;
 import org.apache.zeppelin.interpreter.ExecutionContext;
 import org.apache.zeppelin.interpreter.InterpreterException;
@@ -107,7 +108,8 @@ class NotebookTest extends AbstractInterpreterTest 
implements ParagraphJobListen
     authorizationService = new AuthorizationService(noteManager, zConf, 
storage);
 
     credentials = new Credentials(zConf, storage);
-    notebook = new Notebook(zConf, authorizationService, notebookRepo, 
noteManager, interpreterFactory, interpreterSettingManager, credentials, null);
+    ZeppelinEventBus eventBus = new ZeppelinEventBus();
+    notebook = new Notebook(zConf, authorizationService, notebookRepo, 
noteManager, interpreterFactory, interpreterSettingManager, credentials, 
eventBus);
     notebook.setParagraphJobListener(this);
     schedulerService = new QuartzSchedulerService(zConf, notebook);
     notebook.initNotebook();
diff --git 
a/zeppelin-server/src/test/java/org/apache/zeppelin/notebook/repo/NotebookRepoSyncTest.java
 
b/zeppelin-server/src/test/java/org/apache/zeppelin/notebook/repo/NotebookRepoSyncTest.java
index 3a90e4f603..b7cb75729e 100644
--- 
a/zeppelin-server/src/test/java/org/apache/zeppelin/notebook/repo/NotebookRepoSyncTest.java
+++ 
b/zeppelin-server/src/test/java/org/apache/zeppelin/notebook/repo/NotebookRepoSyncTest.java
@@ -39,6 +39,8 @@ import org.apache.commons.io.FileUtils;
 import org.apache.zeppelin.conf.ZeppelinConfiguration;
 import org.apache.zeppelin.conf.ZeppelinConfiguration.ConfVars;
 import org.apache.zeppelin.display.AngularObjectRegistryListener;
+import org.apache.zeppelin.eventbus.EventBus;
+import org.apache.zeppelin.eventbus.ZeppelinEventBus;
 import org.apache.zeppelin.helium.ApplicationEventListener;
 import org.apache.zeppelin.interpreter.InterpreterFactory;
 import org.apache.zeppelin.interpreter.InterpreterSettingManager;
@@ -76,6 +78,7 @@ class NotebookRepoSyncTest {
   private InterpreterFactory factory;
   private InterpreterSettingManager interpreterSettingManager;
   private Credentials credentials;
+  private EventBus eventBus;
   private AuthenticationInfo anonymous;
   private NoteManager noteManager;
   private AuthorizationService authorizationService;
@@ -116,7 +119,8 @@ class NotebookRepoSyncTest {
     noteManager = new NoteManager(notebookRepoSync, zConf);
     authorizationService = new AuthorizationService(noteManager, zConf, 
storage);
     credentials = new Credentials(zConf, storage);
-    notebook = new Notebook(zConf, authorizationService, notebookRepoSync, 
noteManager, factory, interpreterSettingManager, credentials, null);
+    eventBus = new ZeppelinEventBus();
+    notebook = new Notebook(zConf, authorizationService, notebookRepoSync, 
noteManager, factory, interpreterSettingManager, credentials, eventBus);
     anonymous = new AuthenticationInfo("anonymous");
   }
 
diff --git 
a/zeppelin-server/src/test/java/org/apache/zeppelin/service/NotebookServiceTest.java
 
b/zeppelin-server/src/test/java/org/apache/zeppelin/service/NotebookServiceTest.java
index 1fb67977bc..d9c23718e9 100644
--- 
a/zeppelin-server/src/test/java/org/apache/zeppelin/service/NotebookServiceTest.java
+++ 
b/zeppelin-server/src/test/java/org/apache/zeppelin/service/NotebookServiceTest.java
@@ -49,6 +49,8 @@ import java.util.stream.IntStream;
 
 import org.apache.commons.lang3.StringUtils;
 import org.apache.zeppelin.conf.ZeppelinConfiguration;
+import org.apache.zeppelin.eventbus.EventBus;
+import org.apache.zeppelin.eventbus.ZeppelinEventBus;
 import org.apache.zeppelin.interpreter.Interpreter;
 import org.apache.zeppelin.interpreter.Interpreter.FormType;
 import org.apache.zeppelin.interpreter.InterpreterFactory;
@@ -84,9 +86,6 @@ import org.junit.jupiter.api.Test;
 import org.mockito.ArgumentCaptor;
 import org.junit.jupiter.api.TestInfo;
 
-
-import com.google.gson.Gson;
-
 class NotebookServiceTest {
 
   private static NotebookService notebookService;
@@ -149,6 +148,7 @@ class NotebookServiceTest {
     Credentials credentials = new Credentials();
     NoteManager noteManager = new NoteManager(notebookRepo, zConf);
     authorizationService = new AuthorizationService(noteManager, zConf, 
storage);
+    EventBus eventBus = new ZeppelinEventBus();
     notebook =
         new Notebook(
             zConf,
@@ -158,7 +158,7 @@ class NotebookServiceTest {
             mockInterpreterFactory,
             mockInterpreterSettingManager,
             credentials,
-            null);
+            eventBus);
     searchService = new LuceneSearch(zConf, notebook);
     QuartzSchedulerService schedulerService = new 
QuartzSchedulerService(zConf, notebook);
     notebook.initNotebook();
diff --git 
a/zeppelin-server/src/test/java/org/apache/zeppelin/socket/NotebookServerTest.java
 
b/zeppelin-server/src/test/java/org/apache/zeppelin/socket/NotebookServerTest.java
index 1708fa2044..6aae0af52a 100644
--- 
a/zeppelin-server/src/test/java/org/apache/zeppelin/socket/NotebookServerTest.java
+++ 
b/zeppelin-server/src/test/java/org/apache/zeppelin/socket/NotebookServerTest.java
@@ -56,6 +56,9 @@ import org.apache.thrift.TException;
 import org.apache.zeppelin.conf.ZeppelinConfiguration;
 import org.apache.zeppelin.display.AngularObject;
 import org.apache.zeppelin.display.AngularObjectBuilder;
+import org.apache.zeppelin.eventbus.EventBus;
+import org.apache.zeppelin.eventbus.NoteRemovedEvent;
+import org.apache.zeppelin.eventbus.ZeppelinEventBus;
 import org.apache.zeppelin.interpreter.InterpreterGroup;
 import org.apache.zeppelin.interpreter.InterpreterSetting;
 import org.apache.zeppelin.interpreter.remote.RemoteAngularObjectRegistry;
@@ -74,6 +77,7 @@ import org.apache.zeppelin.common.Message.OP;
 import org.apache.zeppelin.rest.AbstractTestRestApi;
 import org.apache.zeppelin.scheduler.Job;
 import org.apache.zeppelin.scheduler.Job.Status;
+import org.apache.zeppelin.service.JobManagerService;
 import org.apache.zeppelin.service.NotebookService;
 import org.apache.zeppelin.service.ServiceContext;
 import org.apache.zeppelin.user.AuthenticationInfo;
@@ -197,6 +201,32 @@ class NotebookServerTest extends AbstractTestRestApi {
     }
   }
 
+  @Test
+  void testEventBusHandlesNoteRemovedEvent() throws IOException {
+    ZeppelinConfiguration conf = ZeppelinConfiguration.load();
+    
conf.setProperty(ZeppelinConfiguration.ConfVars.ZEPPELIN_EVENTBUS_ENABLED.getVarName(),
 "true");
+
+    EventBus eventBus = new ZeppelinEventBus();
+    NotebookServer server = new NotebookServer();
+    AuthorizationService mockAuthorizationService = 
mock(AuthorizationService.class);
+    JobManagerService mockJobManagerService = mock(JobManagerService.class);
+    Note note = new Note();
+
+    when(mockAuthorizationService.getOwners(note.getId())).thenReturn(new 
HashSet<>());
+
+    server.setZeppelinConfiguration(conf);
+    server.setAuthorizationService(mockAuthorizationService);
+    server.setJobManagerService(() -> mockJobManagerService);
+    server.registerEventBus(eventBus, conf);
+
+    eventBus.post(new NoteRemovedEvent(note, AuthenticationInfo.ANONYMOUS));
+
+    verify(mockJobManagerService).getNoteJobInfoByUnixTime(Mockito.anyLong(), 
Mockito.any(), Mockito.any());
+    verify(mockJobManagerService).removeNoteJobInfo(eq(note.getId()), 
Mockito.isNull(), Mockito.any());
+
+    server.close();
+  }
+
   @Test
   void testOnParagraphCreate_whenJobManagerDisabled() {
     boolean originalFlag = disableJobManagerAndBackupFlag();

Reply via email to