avoid registering several times the same listener on the same client
diff --git a/jspwiki-event/src/main/java/org/apache/wiki/event/WikiEventManager.java b/jspwiki-event/src/main/java/org/apache/wiki/event/WikiEventManager.java
index dc069e9..95728fd 100644
--- a/jspwiki-event/src/main/java/org/apache/wiki/event/WikiEventManager.java
+++ b/jspwiki-event/src/main/java/org/apache/wiki/event/WikiEventManager.java
@@ -29,6 +29,7 @@
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.Vector;
@@ -230,16 +231,17 @@
     }
 
     /**
-     *  Un-registers a WikiEventListener from any WikiEventDelegate client managed by this WikiEventManager. Since this is a one-to-one
-     *  relation, the first match will be returned upon removal; a true return value indicates the WikiEventListener was found and removed.
+     * Un-registers a WikiEventListener from any WikiEventDelegate client managed by this WikiEventManager. A true return value indicates
+     * the WikiEventListener was found and removed.
      *
      * @param listener the event listener
      * @return true if the listener was found and removed.
      */
     public static boolean removeWikiEventListener( final WikiEventListener listener ) {
+        boolean removed = false;
         // get the Map.entry object for the entire Map, then check match on entry (listener)
         final WikiEventManager mgr = getInstance();
-        final Map< Object, WikiEventDelegate > sources = mgr.getDelegates();
+        final Map< Object, WikiEventDelegate > sources =  Collections.synchronizedMap( mgr.getDelegates() );
         synchronized( sources ) {
             // get an iterator over the Map.Enty objects in the map
             for( final Map.Entry< Object, WikiEventDelegate > entry : sources.entrySet() ) {
@@ -248,11 +250,11 @@
 
                 // now see if we can remove the listener from the delegate (delegate may be null because this is a weak reference)
                 if( delegate != null && delegate.removeWikiEventListener( listener ) ) {
-                    return true; // was removed
+                    removed = true; // was removed
                 }
             }
         }
-        return false;
+        return removed;
     }
 
     /**
@@ -387,13 +389,19 @@
         /**
          *  Adds <tt>listener</tt> as a listener for events fired by the WikiEventDelegate.
          *
-         * @param listener   the WikiEventListener to be added
+         * @param listener the WikiEventListener to be added
          * @return true if the listener was added (i.e., it was not already in the list and was added)
          */
         public boolean addWikiEventListener( final WikiEventListener listener ) {
             synchronized( m_listenerList ) {
-                return m_listenerList.add( new WeakReference<>(listener) );
+                final boolean listenerAlreadyContained = m_listenerList.stream()
+                                                                       .map( WeakReference::get )
+                                                                       .anyMatch( ref -> Objects.equals( ref, listener ) );
+                if( !listenerAlreadyContained ) {
+                    return m_listenerList.add( new WeakReference<>( listener ) );
+                }
             }
+            return false;
         }
 
         /**
diff --git a/jspwiki-event/src/test/java/org/apache/wiki/event/TestWikiEventListener.java b/jspwiki-event/src/test/java/org/apache/wiki/event/TestWikiEventListener.java
new file mode 100644
index 0000000..8372e0a
--- /dev/null
+++ b/jspwiki-event/src/test/java/org/apache/wiki/event/TestWikiEventListener.java
@@ -0,0 +1,35 @@
+/*
+    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.wiki.event;
+
+public class TestWikiEventListener implements WikiEventListener {
+
+    int invoked = 0;
+
+    /** {@inheritDoc} */
+    @Override
+    public void actionPerformed( final WikiEvent event ) {
+        invoked++;
+    }
+
+    public int getInvoked() {
+        return invoked;
+    }
+
+}
diff --git a/jspwiki-event/src/test/java/org/apache/wiki/event/WikiEventManagerTest.java b/jspwiki-event/src/test/java/org/apache/wiki/event/WikiEventManagerTest.java
new file mode 100644
index 0000000..8902201
--- /dev/null
+++ b/jspwiki-event/src/test/java/org/apache/wiki/event/WikiEventManagerTest.java
@@ -0,0 +1,71 @@
+/*
+    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.wiki.event;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+
+public class WikiEventManagerTest {
+
+    @Test
+    public void shouldCheckRegisterUnregister() {
+        final String client1 = "test1";
+        final String client2 = "test2";
+        final TestWikiEventListener listener = new TestWikiEventListener();
+        WikiEventManager.addWikiEventListener( client1, listener );
+        Assertions.assertEquals( 1, WikiEventManager.getWikiEventListeners( client1 ).size() );
+        Assertions.assertTrue( WikiEventManager.isListening( client1 ) );
+
+        WikiEventManager.removeWikiEventListener( client1, listener );
+        Assertions.assertEquals( 0, WikiEventManager.getWikiEventListeners( client1 ).size() );
+        Assertions.assertFalse( WikiEventManager.isListening( client1 ) );
+
+        WikiEventManager.addWikiEventListener( client1, listener );
+        WikiEventManager.addWikiEventListener( client2, listener );
+        WikiEventManager.removeWikiEventListener( listener );
+        Assertions.assertEquals( 0, WikiEventManager.getWikiEventListeners( client1 ).size() );
+        Assertions.assertEquals( 0, WikiEventManager.getWikiEventListeners( client2 ).size() );
+    }
+
+    @Test
+    public void shouldCheckAddingSameWikiEventListenerSeveralTimesOnlyGetsRegisteredOnce() {
+        final String client = "test3";
+        final TestWikiEventListener listener = new TestWikiEventListener();
+        WikiEventManager.addWikiEventListener( client, listener );
+        WikiEventManager.addWikiEventListener( client, listener );
+
+        Assertions.assertEquals( 1, WikiEventManager.getWikiEventListeners( client ).size() );
+
+        WikiEventManager.removeWikiEventListener( client, listener );
+        Assertions.assertEquals( 0, WikiEventManager.getWikiEventListeners( client ).size() );
+    }
+
+    @Test
+    public void shouldCheckEventsFiring() {
+        final String client = "test4";
+        final TestWikiEventListener listener = new TestWikiEventListener();
+        WikiEventManager.addWikiEventListener( client, listener );
+        WikiEventManager.fireEvent( client, new WikiPageEvent( "object which fires the event", WikiPageEvent.PAGE_REQUESTED, "page" ) );
+
+        Assertions.assertEquals( 1, listener.getInvoked() );
+        WikiEventManager.removeWikiEventListener( listener ); // dispose listener; if not done, listener would still be attached to test4 on other tests
+    }
+
+}