You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@archiva.apache.org by ma...@apache.org on 2022/02/13 09:27:58 UTC

[archiva] 02/02: Improving event API

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

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

commit 0a2dab16770cce10bda6fa2cc2e2d9c7d64c5dbf
Author: Martin Schreier <ma...@apache.org>
AuthorDate: Sun Feb 13 10:27:32 2022 +0100

    Improving event API
---
 .../archiva-consumer-archetype/pom.xml             |  6 ++
 .../archiva-base/archiva-event-api/pom.xml         | 41 +++++++++
 .../archiva/event/AbstractEventManager.java}       | 58 +++++++------
 .../apache/archiva/event/BasicEventManager.java}   | 26 ++++--
 .../main/java/org/apache/archiva/event/Event.java  | 42 +++++++++-
 .../org/apache/archiva/event/EventContext.java     | 48 +++++++++++
 .../apache/archiva/event/EventContextBuilder.java  | 72 ++++++++++++++++
 .../org/apache/archiva/event/EventHandler.java     |  0
 .../java/org/apache/archiva/event/EventSource.java |  0
 .../java/org/apache/archiva/event/EventType.java   | 15 +++-
 .../archiva/event/context/RepositoryContext.java   | 90 ++++++++++++++++++++
 .../apache/archiva/event/context/RestContext.java  | 96 ++++++++++++++++++++++
 .../apache/archiva/event/context/UserContext.java  | 71 ++++++++++++++++
 .../org/apache/archiva/event/package-info.java     | 37 +++++++++
 .../archiva/event/BasicEventManagerTest.java}      | 11 ++-
 .../archiva-base/archiva-event-central/pom.xml     | 44 ++++++++++
 .../event/central/CentralEventManager.java}        | 37 ++++-----
 .../src/main/resources/META-INF/spring-context.xml | 32 ++++++++
 .../archiva-repository-admin-default/pom.xml       |  4 +
 .../archiva-base/archiva-repository-api/pom.xml    |  4 +
 .../archiva/repository/RepositoryHandler.java      |  4 +-
 .../archiva/repository/event/RepositoryEvent.java  | 22 +++++
 .../archiva-base/archiva-repository-layer/pom.xml  |  4 +
 .../repository/base/AbstractRepository.java        |  8 +-
 .../repository/base/AbstractRepositoryHandler.java | 10 +--
 .../repository/base/ArchivaRepositoryRegistry.java | 20 +++--
 .../base/managed/ManagedRepositoryHandler.java     |  2 +
 .../base/group/RepositoryGroupHandlerTest.java     |  4 +-
 archiva-modules/archiva-base/pom.xml               |  2 +
 .../archiva-maven/archiva-maven-repository/pom.xml |  4 +
 .../maven/repository/MavenRepositoryProvider.java  | 12 ++-
 .../rest/api/v2/model/MavenManagedRepository.java  | 31 -------
 .../api/v2/model/MavenManagedRepositoryUpdate.java |  9 +-
 .../api/v2/model/map/MavenRepositoryMapper.java    | 12 ++-
 .../apache/archiva/rest/api/v2/svc/ErrorKeys.java  |  6 ++
 .../v2/model/map/MavenRepositoryMapperTest.java    | 45 ++++++++++
 .../archiva-rest/archiva-rest-services/pom.xml     |  4 +
 .../archiva/rest/v2/svc/AbstractService.java       | 51 ++++++++++++
 .../DefaultMavenManagedRepositoryService.java      | 57 ++++++-------
 .../NativeMavenManagedRepositoryServiceTest.java   |  3 +-
 pom.xml                                            | 10 +++
 41 files changed, 893 insertions(+), 161 deletions(-)

diff --git a/archiva-modules/archiva-base/archiva-consumers/archiva-consumer-archetype/pom.xml b/archiva-modules/archiva-base/archiva-consumers/archiva-consumer-archetype/pom.xml
index 4284456..6e2f82f 100644
--- a/archiva-modules/archiva-base/archiva-consumers/archiva-consumer-archetype/pom.xml
+++ b/archiva-modules/archiva-base/archiva-consumers/archiva-consumer-archetype/pom.xml
@@ -76,6 +76,12 @@
       <artifactId>jcl-over-slf4j</artifactId>
     </dependency>
 
+    <!-- Test scope -->
+    <dependency>
+      <groupId>org.apache.archiva.event</groupId>
+      <artifactId>archiva-event-api</artifactId>
+      <scope>test</scope>
+    </dependency>
     <dependency>
       <groupId>org.apache.archiva</groupId>
       <artifactId>archiva-consumer-api</artifactId>
diff --git a/archiva-modules/archiva-base/archiva-event-api/pom.xml b/archiva-modules/archiva-base/archiva-event-api/pom.xml
new file mode 100644
index 0000000..6c9119c
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-event-api/pom.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <parent>
+    <artifactId>archiva-base</artifactId>
+    <groupId>org.apache.archiva</groupId>
+    <version>3.0.0-SNAPSHOT</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+
+  <name>Archiva :: Base :: Event API</name>
+  <groupId>org.apache.archiva.event</groupId>
+  <artifactId>archiva-event-api</artifactId>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.archiva</groupId>
+      <artifactId>archiva-common</artifactId>
+    </dependency>
+  </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/event/EventManager.java b/archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/AbstractEventManager.java
similarity index 50%
rename from archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/event/EventManager.java
rename to archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/AbstractEventManager.java
index 00a2400..6d2ddff 100644
--- a/archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/event/EventManager.java
+++ b/archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/AbstractEventManager.java
@@ -1,5 +1,4 @@
 package org.apache.archiva.event;
-
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -9,8 +8,7 @@ package org.apache.archiva.event;
  * "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
- *
+ * 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
@@ -22,41 +20,47 @@ package org.apache.archiva.event;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.*;
+import java.util.LinkedHashSet;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
-public class EventManager implements EventSource
+/**
+ * @author Martin Schreier <ma...@apache.org>
+ */
+public class AbstractEventManager implements EventSource
 {
+    private static final Logger log = LoggerFactory.getLogger( AbstractEventManager.class );
 
-    private static final Logger LOG = LoggerFactory.getLogger(EventManager.class);
-
-    private final ConcurrentHashMap<EventType<? extends Event>, Set<EventHandler>> handlerMap = new ConcurrentHashMap<>();
-
-    private final Object source;
-
-    public EventManager(Object source) {
-        if (source==null) {
-            throw new IllegalArgumentException("The source may not be null");
-        }
-        this.source = source;
-    }
+    protected final ConcurrentHashMap<EventType<? extends Event>, Set<EventHandler>> handlerMap = new ConcurrentHashMap<>();
 
     @Override
-    public <T extends Event> void registerEventHandler(EventType<T> type, EventHandler<? super T> eventHandler) {
+    public <T extends Event> void registerEventHandler( EventType<T> type, EventHandler<? super T> eventHandler) {
         Set<EventHandler> handlers = handlerMap.computeIfAbsent(type, t -> new LinkedHashSet<>());
         if (!handlers.contains(eventHandler)) {
             handlers.add(eventHandler);
         }
+        log.debug( "Event handler registered: " + eventHandler.getClass( ) );
     }
 
     @Override
-    public <T extends Event> void unregisterEventHandler(EventType<T> type, EventHandler<? super T> eventHandler) {
+    public <T extends Event> void unregisterEventHandler( EventType<T> type, EventHandler<? super T> eventHandler) {
         if (handlerMap.containsKey(type)) {
             handlerMap.get(type).remove(eventHandler);
+            log.debug( "Event handler unregistered: " + eventHandler.getClass( ) );
         }
     }
 
-    public void fireEvent(Event fireEvent) {
+    /**
+     * Fires the given event for the given source. If the source of the provided event does not match the <code>source</code>
+     * parameter the event will be chained.
+     *
+     * The event will be sent to all registered event handler. Exceptions during handling are not propagated to the
+     * caller.
+     *
+     * @param fireEvent the event to fire
+     * @param source the source object
+     */
+    public void fireEvent(Event fireEvent, Object source) {
         final EventType<? extends Event> type = fireEvent.getType();
         Event event;
         if (fireEvent.getSource()!=source) {
@@ -66,14 +70,14 @@ public class EventManager implements EventSource
         }
         for (EventType<? extends Event> handlerType : handlerMap.keySet()) {
             if (EventType.isInstanceOf(type, handlerType)) {
-                    for (EventHandler handler : handlerMap.get(handlerType)) {
-                        try {
-                            handler.handle(event);
-                        } catch (Exception e) {
-                            // We catch all errors from handlers
-                            LOG.error("An error occured during event handling: {}", e.getMessage(), e);
-                        }
+                for (EventHandler handler : handlerMap.get(handlerType)) {
+                    try {
+                        handler.handle(event);
+                    } catch (Throwable e) {
+                        // We catch all errors from handlers
+                        log.error("An error occured during event handling: {}", e.getMessage(), e);
                     }
+                }
             }
         }
     }
diff --git a/archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/event/EventHandler.java b/archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/BasicEventManager.java
similarity index 56%
copy from archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/event/EventHandler.java
copy to archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/BasicEventManager.java
index f98adcd..f952b9e 100644
--- a/archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/event/EventHandler.java
+++ b/archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/BasicEventManager.java
@@ -9,8 +9,7 @@ package org.apache.archiva.event;
  * "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
- *
+ * 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
@@ -19,13 +18,24 @@ package org.apache.archiva.event;
  * under the License.
  */
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-import java.util.EventListener;
+public class BasicEventManager extends AbstractEventManager implements EventSource
+{
 
-/**
- * A listener that accepts events.
- */
-public interface EventHandler<T extends Event> extends EventListener {
+    private static final Logger LOG = LoggerFactory.getLogger( BasicEventManager.class);
+
+    private final Object source;
+
+    public BasicEventManager( Object source) {
+        if (source==null) {
+            throw new IllegalArgumentException("The source may not be null");
+        }
+        this.source = source;
+    }
 
-    void handle(T event);
+    public void fireEvent(Event fireEvent) {
+        super.fireEvent( fireEvent, source );
+    }
 }
diff --git a/archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/event/Event.java b/archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/Event.java
similarity index 67%
rename from archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/event/Event.java
rename to archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/Event.java
index eda8570..2439105 100644
--- a/archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/event/Event.java
+++ b/archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/Event.java
@@ -9,8 +9,7 @@ package org.apache.archiva.event;
  * "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
- *
+ * 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
@@ -21,6 +20,9 @@ package org.apache.archiva.event;
 
 import java.time.LocalDateTime;
 import java.util.EventObject;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * Base class for events. Events have a type and a source.
@@ -41,6 +43,8 @@ public class Event extends EventObject implements Cloneable {
     private final EventType<? extends Event> type;
     private final LocalDateTime createTime;
 
+    private HashMap<Class<? extends EventContext>, EventContext> contextMap = new HashMap<>( );
+
     public Event(EventType<? extends Event> type, Object originator) {
         super(originator);
         this.type = type;
@@ -52,6 +56,7 @@ public class Event extends EventObject implements Cloneable {
         this.previous = previous;
         this.type = previous.getType();
         this.createTime = previous.getCreateTime();
+        this.contextMap = previous.contextMap;
     }
 
     /**
@@ -70,6 +75,38 @@ public class Event extends EventObject implements Cloneable {
         return createTime;
     }
 
+    public <T extends EventContext> T getContext(Class<T> contextClazz) throws IllegalArgumentException {
+        if (contextMap.containsKey( contextClazz )) {
+            return (T) contextMap.get( contextClazz );
+        } else {
+            T ctx = null;
+            for ( Map.Entry<Class<? extends EventContext>, EventContext> clazzEntry : contextMap.entrySet()) {
+                if ( contextClazz.isAssignableFrom( clazzEntry.getKey() ) )
+                {
+                    ctx = (T) clazzEntry.getValue( );
+                    break;
+                }
+            }
+            if (ctx!=null) {
+                contextMap.put( contextClazz, ctx );
+                return ctx;
+            }
+        }
+        throw new IllegalArgumentException( "No matching event context registered for " + contextClazz );
+    }
+
+    public Map<String, String> getContextData() {
+        return contextMap.entrySet( ).stream( ).flatMap( ctx -> ctx.getValue( ).getData( ).entrySet( ).stream( ) )
+            .collect( Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue ) );
+    }
+
+    public <T extends EventContext> void setContext( Class<T> clazz, T context) {
+        this.contextMap.put( clazz, context );
+    }
+
+    public <T extends EventContext> void setContext( T context) {
+        this.contextMap.put( context.getClass(), context );
+    }
 
     /**
      * Recreates the event with the given instance as the new source. The
@@ -81,6 +118,7 @@ public class Event extends EventObject implements Cloneable {
         Event newEvent = (Event) this.clone();
         newEvent.previous = this;
         newEvent.source = newSource;
+        newEvent.contextMap = this.contextMap;
         return newEvent;
     }
 
diff --git a/archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/EventContext.java b/archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/EventContext.java
new file mode 100644
index 0000000..9e113de
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/EventContext.java
@@ -0,0 +1,48 @@
+package org.apache.archiva.event;
+/*
+ * 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.
+ */
+
+import java.util.Map;
+
+/**
+ * Context information about a specific event.
+ * This is used to provide specific information about the event context by using the generic
+ * event interface.
+ * Some event handler may need information about the underlying event but have no access to the
+ * API classes that represent the event.
+ *
+ * Context information is always string based and should not depend on external classes apart from JDK classes.
+ *
+ * @author Martin Schreier <ma...@apache.org>
+ */
+public interface EventContext
+{
+    /**
+     * Returns the prefix used for entry keys in the repository data map.
+     * @return the prefix string for this context
+     */
+    String getPrefix();
+
+    /**
+     * Returns the context data as map of strings. Each entry key is prefixed with
+     * the unique prefix of this context.
+     *
+     * @return the map of key value pairs stored in this context
+     */
+    Map<String,String> getData();
+}
diff --git a/archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/EventContextBuilder.java b/archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/EventContextBuilder.java
new file mode 100644
index 0000000..41f10ce
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/EventContextBuilder.java
@@ -0,0 +1,72 @@
+package org.apache.archiva.event;
+/*
+ * 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.
+ */
+
+import org.apache.archiva.event.context.RepositoryContext;
+import org.apache.archiva.event.context.RestContext;
+import org.apache.archiva.event.context.UserContext;
+
+/**
+ * Static helper class that allows to set certain context data
+ *
+ * @author Martin Schreier <ma...@apache.org>
+ */
+public class EventContextBuilder
+{
+    Event evt;
+
+    public static void setUserContext(Event evt, String user, String remoteAddress) {
+        evt.setContext( UserContext.class, new UserContext( user, remoteAddress ) );
+    }
+
+    public static void setRestcontext(Event evt, String service, String path, String operation, int resultCode, String... parameters ) {
+        evt.setContext( RestContext.class, new RestContext( service, path, operation, resultCode, parameters ) );
+    }
+
+    public static void setRepositoryContext(Event evt, String id, String type, String flavour ) {
+        evt.setContext( RepositoryContext.class, new RepositoryContext( id, type, flavour ) );
+    }
+
+    private EventContextBuilder( Event evt) {
+        this.evt = evt;
+    }
+
+    public static EventContextBuilder withEvent( Event evt )
+    {
+        return new EventContextBuilder( evt );
+    }
+
+    public EventContextBuilder withUser( String user, String remoteAddress) {
+        setUserContext( this.evt, user, remoteAddress );
+        return this;
+    }
+
+    public EventContextBuilder witRest( String service, String path, String operation, int resultCode, String... parameters) {
+        setRestcontext( this.evt, service, path, operation, resultCode, parameters );
+        return this;
+    }
+
+    public EventContextBuilder withRepository(String id, String type, String flavour) {
+        setRepositoryContext( this.evt, id, type, flavour );
+        return this;
+    }
+
+    public Event apply() {
+        return this.evt;
+    }
+}
diff --git a/archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/event/EventHandler.java b/archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/EventHandler.java
similarity index 100%
rename from archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/event/EventHandler.java
rename to archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/EventHandler.java
diff --git a/archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/event/EventSource.java b/archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/EventSource.java
similarity index 100%
rename from archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/event/EventSource.java
rename to archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/EventSource.java
diff --git a/archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/event/EventType.java b/archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/EventType.java
similarity index 93%
rename from archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/event/EventType.java
rename to archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/EventType.java
index 50d4267..6927c81 100644
--- a/archiva-modules/archiva-base/archiva-common/src/main/java/org/apache/archiva/event/EventType.java
+++ b/archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/EventType.java
@@ -9,8 +9,7 @@ package org.apache.archiva.event;
  * "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
- *
+ * 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
@@ -82,6 +81,18 @@ public class EventType<T extends Event> implements Serializable  {
         return superType;
     }
 
+    public String getPath() {
+        List<String> path = new ArrayList<>( );
+        EventType eventType = this;
+        while(eventType!=ROOT)
+        {
+            path.add( eventType.name( ) );
+            eventType = this.getSuperType( );
+        }
+        Collections.reverse( path );
+        return String.join( "/", path );
+    }
+
     private void register(EventType<? extends T> subType) {
         if (subTypes == null) {
             subTypes = new WeakHashMap<>();
diff --git a/archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/context/RepositoryContext.java b/archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/context/RepositoryContext.java
new file mode 100644
index 0000000..4df023d
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/context/RepositoryContext.java
@@ -0,0 +1,90 @@
+package org.apache.archiva.event.context;
+/*
+ * 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.
+ */
+
+import org.apache.archiva.event.EventContext;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This context provides repository data.
+ *
+ * @author Martin Schreier <ma...@apache.org>
+ */
+public class RepositoryContext implements EventContext, Serializable
+{
+    private static final long serialVersionUID = -4172663291198878307L;
+
+    private static final String PREFIX = "repository";
+
+    private final String id;
+    private final String type;
+    private final String flavour;
+
+    public RepositoryContext( String id, String type, String flavour )
+    {
+        this.id = id;
+        this.type = type;
+        this.flavour = flavour;
+    }
+
+    /**
+     * Returns the repository id
+     * @return the repository id
+     */
+    public String getId( )
+    {
+        return id;
+    }
+
+    /**
+     * Returns the repository type (e.g. MAVEN)
+     * @return the string representation of the repository type
+     */
+    public String getType( )
+    {
+        return type;
+    }
+
+    /**
+     * Returns the repository flavour (e.g. Remote, Managed, Group)
+     * @return
+     */
+    public String getFlavour( )
+    {
+        return flavour;
+    }
+
+    @Override
+    public Map<String, String> getData( )
+    {
+        Map<String, String> values = new HashMap<>( );
+        values.put( PREFIX+".id", id );
+        values.put( PREFIX+".type", type );
+        values.put( PREFIX+".flavour", flavour );
+        return values;
+    }
+
+    @Override
+    public String getPrefix( )
+    {
+        return PREFIX;
+    }
+}
diff --git a/archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/context/RestContext.java b/archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/context/RestContext.java
new file mode 100644
index 0000000..615759d
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/context/RestContext.java
@@ -0,0 +1,96 @@
+package org.apache.archiva.event.context;
+/*
+ * 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.
+ */
+
+import org.apache.archiva.event.EventContext;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Provides information about a REST call.
+ *
+ * @author Martin Schreier <ma...@apache.org>
+ */
+public class RestContext implements EventContext, Serializable
+{
+    private static final long serialVersionUID = -4109505194250928317L;
+
+    public static final String PREFIX = "rest";
+
+    private final String service;
+    private final String path;
+    private final String operation;
+    private final List<String> parameters;
+    private final int resultCode;
+
+
+    public RestContext( String service, String path, String operation, int resultCode, String... parameters )
+    {
+        this.service = service;
+        this.path = path;
+        this.operation = operation;
+        this.resultCode = resultCode;
+        this.parameters = Arrays.asList( parameters );
+    }
+
+    public String getService( )
+    {
+        return service;
+    }
+
+    public String getPath( )
+    {
+        return path;
+    }
+
+    public String getOperation( )
+    {
+        return operation;
+    }
+
+    public List<String> getParameters( )
+    {
+        return parameters;
+    }
+
+    public int getResultCode( )
+    {
+        return resultCode;
+    }
+
+    @Override
+    public Map<String, String> getData( )
+    {
+        Map<String, String> values = new HashMap<>( );
+        values.put( PREFIX+".service", service );
+        values.put( PREFIX+".path", path );
+        values.put( PREFIX+".operation", operation );
+        values.put( PREFIX+".parameter", String.join( ",", parameters ) );
+        return values;
+    }
+
+    @Override
+    public String getPrefix( )
+    {
+        return PREFIX;
+    }
+}
diff --git a/archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/context/UserContext.java b/archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/context/UserContext.java
new file mode 100644
index 0000000..8a83c73
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/context/UserContext.java
@@ -0,0 +1,71 @@
+package org.apache.archiva.event.context;
+/*
+ * 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.
+ */
+
+import org.apache.archiva.event.EventContext;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This context provides user information.
+ *
+ * @author Martin Schreier <ma...@apache.org>
+ */
+public class UserContext implements EventContext, Serializable
+{
+    private static final long serialVersionUID = -3499164111736559781L;
+
+    private static final String PREFIX = "user";
+
+    private final String userId;
+    private final String remoteAddress;
+
+    public UserContext( String user, String remoteAddress )
+    {
+        this.userId = user == null ? "" : user;
+        this.remoteAddress = remoteAddress == null ? "" : remoteAddress;
+
+    }
+
+    public String getUserId( )
+    {
+        return userId;
+    }
+
+    public String getRemoteAddress( )
+    {
+        return remoteAddress;
+    }
+
+    @Override
+    public Map<String, String> getData( )
+    {
+        Map<String, String> values = new HashMap<>( );
+        values.put( PREFIX+".user_id", userId );
+        values.put( PREFIX+".remote_address", remoteAddress );
+        return values;
+    }
+
+    @Override
+    public String getPrefix( )
+    {
+        return PREFIX;
+    }
+}
diff --git a/archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/package-info.java b/archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/package-info.java
new file mode 100644
index 0000000..30db350
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/package-info.java
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+/**
+ * This module provides an event mechanism for all archiva subsystems.
+ *
+ * The events are hierarchical organized. That means each subsystem has its own event manager that collects events
+ * and processes and forwards them to the parent event manager (normally the central event manager).
+ * Each event manager clones the event and stores the origin event in the chain before forwarding them to the parent manager.
+ *
+ * Event Types are also hierarchical. There is one special type {@link org.apache.archiva.event.EventType#ROOT} that is the
+ * root type and has no parent type. All other types must be descendants of the ROOT type.
+ *
+ * Event types may have certain methods to access context information. But context information can also be accessed in a
+ * subsystem independent way using the event context data. Event contexts provide access to data without using the
+ * subsystem API and classes.
+ * Event types may be used for filtering events.
+ *
+ * @since 3.0
+ * @author Martin Schreier <ma...@apache.org>
+ */
+package org.apache.archiva.event;
\ No newline at end of file
diff --git a/archiva-modules/archiva-base/archiva-common/src/test/java/org/apache/archiva/event/EventManagerTest.java b/archiva-modules/archiva-base/archiva-event-api/src/test/java/org/apache/archiva/event/BasicEventManagerTest.java
similarity index 94%
rename from archiva-modules/archiva-base/archiva-common/src/test/java/org/apache/archiva/event/EventManagerTest.java
rename to archiva-modules/archiva-base/archiva-event-api/src/test/java/org/apache/archiva/event/BasicEventManagerTest.java
index f894cb5..6e0ba61 100644
--- a/archiva-modules/archiva-base/archiva-common/src/test/java/org/apache/archiva/event/EventManagerTest.java
+++ b/archiva-modules/archiva-base/archiva-event-api/src/test/java/org/apache/archiva/event/BasicEventManagerTest.java
@@ -9,8 +9,7 @@ package org.apache.archiva.event;
  * "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
- *
+ * 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
@@ -29,7 +28,7 @@ import static org.junit.jupiter.api.Assertions.*;
 /**
  * @author Martin Stockhammer <ma...@apache.org>
  */
-public class EventManagerTest
+public class BasicEventManagerTest
 {
 
     private class TestHandler implements EventHandler<Event> {
@@ -53,7 +52,7 @@ public class EventManagerTest
     @Test
     public void registerEventHandler( )
     {
-        EventManager eventManager = new EventManager( this );
+        BasicEventManager eventManager = new BasicEventManager( this );
         TestHandler handler1 = new TestHandler( );
         TestHandler handler2 = new TestHandler( );
         TestHandler handler3 = new TestHandler( );
@@ -92,7 +91,7 @@ public class EventManagerTest
     @Test
     public void unregisterEventHandler( )
     {
-        EventManager eventManager = new EventManager( this );
+        BasicEventManager eventManager = new BasicEventManager( this );
         TestHandler handler1 = new TestHandler( );
         TestHandler handler2 = new TestHandler( );
         TestHandler handler3 = new TestHandler( );
@@ -124,7 +123,7 @@ public class EventManagerTest
     public void fireEvent( )
     {
         Object other = new Object( );
-        EventManager eventManager = new EventManager( this );
+        BasicEventManager eventManager = new BasicEventManager( this );
         assertThrows( NullPointerException.class, ( ) -> eventManager.fireEvent( null ) );
         Event event = new Event( EventType.ROOT, other );
         assertEquals( other, event.getSource( ) );
diff --git a/archiva-modules/archiva-base/archiva-event-central/pom.xml b/archiva-modules/archiva-base/archiva-event-central/pom.xml
new file mode 100644
index 0000000..cd8a5b0
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-event-central/pom.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <parent>
+    <artifactId>archiva-base</artifactId>
+    <groupId>org.apache.archiva</groupId>
+    <version>3.0.0-SNAPSHOT</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>org.apache.archiva.event</groupId>
+  <artifactId>archiva-event-central</artifactId>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.archiva.event</groupId>
+      <artifactId>archiva-event-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-context</artifactId>
+    </dependency>
+  </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/v2/model/MavenManagedRepositoryUpdate.java b/archiva-modules/archiva-base/archiva-event-central/src/main/java/org/apache/archiva/event/central/CentralEventManager.java
similarity index 50%
copy from archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/v2/model/MavenManagedRepositoryUpdate.java
copy to archiva-modules/archiva-base/archiva-event-central/src/main/java/org/apache/archiva/event/central/CentralEventManager.java
index 5536ae9..44ba9df 100644
--- a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/v2/model/MavenManagedRepositoryUpdate.java
+++ b/archiva-modules/archiva-base/archiva-event-central/src/main/java/org/apache/archiva/event/central/CentralEventManager.java
@@ -1,4 +1,4 @@
-package org.apache.archiva.rest.api.v2.model;
+package org.apache.archiva.event.central;
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -17,31 +17,26 @@ package org.apache.archiva.rest.api.v2.model;
  * under the License.
  */
 
-import org.apache.archiva.repository.ManagedRepository;
-
-import java.io.Serializable;
+import org.apache.archiva.event.AbstractEventManager;
+import org.apache.archiva.event.Event;
+import org.apache.archiva.event.EventHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
 
 /**
- * @author Martin Stockhammer <ma...@apache.org>
+ * Event manager that collects all events from archiva subsystems.
+ *
+ * @author Martin Schreier <ma...@apache.org>
  */
-public class MavenManagedRepositoryUpdate extends MavenManagedRepository implements Serializable
+@Service("eventManager#archiva")
+public class CentralEventManager extends AbstractEventManager implements EventHandler<Event>
 {
-    private static final long serialVersionUID = -9181643343284109862L;
-    private boolean resetStats = false;
-
-    public static MavenManagedRepositoryUpdate of( ManagedRepository repository ) {
-        MavenManagedRepositoryUpdate repo = new MavenManagedRepositoryUpdate( );
-        update( repo, repository );
-        return repo;
-    }
-
-    public boolean isResetStats( )
-    {
-        return resetStats;
-    }
+    private static final Logger log = LoggerFactory.getLogger( CentralEventManager.class );
 
-    public void setResetStats( boolean resetStats )
+    @Override
+    public void handle( Event event )
     {
-        this.resetStats = resetStats;
+        log.info( "Event: type={}, sourceClass={}, source={}", event.getType( ), event.getSource().getClass(), event.getSource() );
     }
 }
diff --git a/archiva-modules/archiva-base/archiva-event-central/src/main/resources/META-INF/spring-context.xml b/archiva-modules/archiva-base/archiva-event-central/src/main/resources/META-INF/spring-context.xml
new file mode 100644
index 0000000..654b2f8
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-event-central/src/main/resources/META-INF/spring-context.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+
+<!--
+  ~ 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.
+  -->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+           http://www.springframework.org/schema/beans/spring-beans.xsd
+           http://www.springframework.org/schema/context
+           http://www.springframework.org/schema/context/spring-context.xsd"
+       default-lazy-init="true">
+
+  <context:annotation-config />
+  <context:component-scan base-package="org.apache.archiva.event.central"/>
+
+</beans>
\ No newline at end of file
diff --git a/archiva-modules/archiva-base/archiva-repository-admin/archiva-repository-admin-default/pom.xml b/archiva-modules/archiva-base/archiva-repository-admin/archiva-repository-admin-default/pom.xml
index 8c231f2..b34922d 100644
--- a/archiva-modules/archiva-base/archiva-repository-admin/archiva-repository-admin-default/pom.xml
+++ b/archiva-modules/archiva-base/archiva-repository-admin/archiva-repository-admin-default/pom.xml
@@ -34,6 +34,10 @@
 
   <dependencies>
     <dependency>
+      <groupId>org.apache.archiva.event</groupId>
+      <artifactId>archiva-event-api</artifactId>
+    </dependency>
+    <dependency>
       <groupId>org.apache.archiva.configuration</groupId>
       <artifactId>archiva-configuration-provider</artifactId>
     </dependency>
diff --git a/archiva-modules/archiva-base/archiva-repository-api/pom.xml b/archiva-modules/archiva-base/archiva-repository-api/pom.xml
index 4ae27d9..820525f 100644
--- a/archiva-modules/archiva-base/archiva-repository-api/pom.xml
+++ b/archiva-modules/archiva-base/archiva-repository-api/pom.xml
@@ -35,6 +35,10 @@
   <dependencies>
 
     <dependency>
+      <groupId>org.apache.archiva.event</groupId>
+      <artifactId>archiva-event-api</artifactId>
+    </dependency>
+    <dependency>
       <groupId>org.apache.archiva</groupId>
       <artifactId>archiva-common</artifactId>
      </dependency>
diff --git a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/RepositoryHandler.java b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/RepositoryHandler.java
index 8ed6c37..4b877d9 100644
--- a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/RepositoryHandler.java
+++ b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/RepositoryHandler.java
@@ -277,11 +277,11 @@ public interface RepositoryHandler<R extends Repository, C extends AbstractRepos
      * Returns the repository variant, this handler manages.
      * @return the concrete variant class
      */
-    Class<R> getVariant();
+    Class<R> getFlavour();
 
     /**
      * Returns the repository configuration variant, this handler manages.
      * @return the concrete configuration variant class
      */
-    Class<C> getConfigurationVariant();
+    Class<C> getConfigurationFlavour();
 }
diff --git a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/event/RepositoryEvent.java b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/event/RepositoryEvent.java
index 88c95ad..a4b667b 100644
--- a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/event/RepositoryEvent.java
+++ b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/event/RepositoryEvent.java
@@ -20,8 +20,12 @@ package org.apache.archiva.repository.event;
  */
 
 import org.apache.archiva.event.Event;
+import org.apache.archiva.event.EventContextBuilder;
 import org.apache.archiva.event.EventType;
+import org.apache.archiva.repository.ManagedRepository;
+import org.apache.archiva.repository.RemoteRepository;
 import org.apache.archiva.repository.Repository;
+import org.apache.archiva.repository.RepositoryGroup;
 
 /**
  * A repository event is specific to a repository and holds a reference to the repository that
@@ -39,6 +43,12 @@ public class RepositoryEvent extends Event
     public RepositoryEvent(EventType<? extends RepositoryEvent> type, Object origin, Repository repository) {
         super(type, origin);
         this.repository = repository;
+        EventContextBuilder builder = EventContextBuilder.withEvent( this );
+        if (repository!=null)
+        {
+            builder.withRepository( repository.getId( ), repository.getType( ).name( ), getFlavour( repository ) );
+        }
+        builder.apply( );
     }
 
     public Repository getRepository() {
@@ -49,4 +59,16 @@ public class RepositoryEvent extends Event
     public EventType<? extends RepositoryEvent> getType() {
         return (EventType<? extends RepositoryEvent>) super.getType();
     }
+
+    private String getFlavour(Repository repository) {
+        if (repository instanceof RemoteRepository ) {
+            return RemoteRepository.class.getName( );
+        } else if (repository instanceof ManagedRepository ) {
+            return ManagedRepository.class.getName( );
+        } else if ( repository instanceof RepositoryGroup ) {
+            return RepositoryGroup.class.getName( );
+        } else {
+            return "UNKNOWN";
+        }
+    }
 }
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/pom.xml b/archiva-modules/archiva-base/archiva-repository-layer/pom.xml
index b9b6f45..8c424f6 100644
--- a/archiva-modules/archiva-base/archiva-repository-layer/pom.xml
+++ b/archiva-modules/archiva-base/archiva-repository-layer/pom.xml
@@ -70,6 +70,10 @@
       <artifactId>archiva-common</artifactId>
     </dependency>
     <dependency>
+      <groupId>org.apache.archiva.event</groupId>
+      <artifactId>archiva-event-central</artifactId>
+    </dependency>
+    <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-context</artifactId>
     </dependency>
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/AbstractRepository.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/AbstractRepository.java
index ca15e5f..7a10b73 100644
--- a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/AbstractRepository.java
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/AbstractRepository.java
@@ -24,7 +24,7 @@ import com.cronutils.model.definition.CronDefinition;
 import com.cronutils.model.definition.CronDefinitionBuilder;
 import org.apache.archiva.event.Event;
 import org.apache.archiva.event.EventHandler;
-import org.apache.archiva.event.EventManager;
+import org.apache.archiva.event.BasicEventManager;
 import org.apache.archiva.event.EventType;
 import org.apache.archiva.indexer.ArchivaIndexingContext;
 import org.apache.archiva.repository.EditableRepository;
@@ -86,7 +86,7 @@ public abstract class AbstractRepository implements EditableRepository, EventHan
     public static final CronDefinition CRON_DEFINITION = CronDefinitionBuilder.instanceDefinitionFor(CronType.QUARTZ);
     private RepositoryState state;
 
-    private final EventManager eventManager;
+    private final BasicEventManager eventManager;
 
     Map<Class<? extends RepositoryFeature<?>>, RepositoryFeature<?>> featureMap = new HashMap<>(  );
 
@@ -100,7 +100,7 @@ public abstract class AbstractRepository implements EditableRepository, EventHan
         this.storage = repositoryStorage;
         this.location = repositoryStorage.getLocation();
         this.openStatus.compareAndSet(false, true);
-        this.eventManager = new EventManager(this);
+        this.eventManager = new BasicEventManager(this);
     }
 
     public AbstractRepository(Locale primaryLocale, RepositoryType type, String id, String name, RepositoryStorage repositoryStorage) {
@@ -111,7 +111,7 @@ public abstract class AbstractRepository implements EditableRepository, EventHan
         this.storage = repositoryStorage;
         this.location = repositoryStorage.getLocation();
         this.openStatus.compareAndSet(false, true);
-        this.eventManager = new EventManager(this);
+        this.eventManager = new BasicEventManager(this);
     }
 
     protected void setPrimaryLocale(Locale locale) {
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/AbstractRepositoryHandler.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/AbstractRepositoryHandler.java
index 0e17aac..2ffeac2 100644
--- a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/AbstractRepositoryHandler.java
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/AbstractRepositoryHandler.java
@@ -22,7 +22,7 @@ import org.apache.archiva.configuration.model.AbstractRepositoryConfiguration;
 import org.apache.archiva.configuration.model.Configuration;
 import org.apache.archiva.configuration.provider.IndeterminateConfigurationException;
 import org.apache.archiva.event.Event;
-import org.apache.archiva.event.EventManager;
+import org.apache.archiva.event.BasicEventManager;
 import org.apache.archiva.event.EventType;
 import org.apache.archiva.repository.EditableRepository;
 import org.apache.archiva.repository.Repository;
@@ -61,14 +61,14 @@ public abstract class AbstractRepositoryHandler<R extends Repository, C extends
     private CombinedValidator<R> combinedValidator;
     private final Class<R> repositoryClazz;
     private final Class<C> configurationClazz;
-    private final EventManager eventManager;
+    private final BasicEventManager eventManager;
     private final Map<String, R> repositoryMap  = new HashMap<>(  );
     private final ConfigurationHandler configurationHandler;
 
     public AbstractRepositoryHandler(Class<R> repositoryClazz, Class<C> configurationClazz, ConfigurationHandler configurationHandler) {
         this.repositoryClazz = repositoryClazz;
         this.configurationClazz = configurationClazz;
-        this.eventManager = new EventManager( this );
+        this.eventManager = new BasicEventManager( this );
         this.configurationHandler = configurationHandler;
     }
 
@@ -142,13 +142,13 @@ public abstract class AbstractRepositoryHandler<R extends Repository, C extends
     }
 
     @Override
-    public Class<R> getVariant( )
+    public Class<R> getFlavour( )
     {
         return this.repositoryClazz;
     }
 
     @Override
-    public Class<C> getConfigurationVariant( )
+    public Class<C> getConfigurationFlavour( )
     {
         return this.configurationClazz;
     }
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/ArchivaRepositoryRegistry.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/ArchivaRepositoryRegistry.java
index 1d0c322..c3a3c0b 100644
--- a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/ArchivaRepositoryRegistry.java
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/ArchivaRepositoryRegistry.java
@@ -31,8 +31,10 @@ import org.apache.archiva.configuration.model.RemoteRepositoryConfiguration;
 import org.apache.archiva.configuration.model.RepositoryGroupConfiguration;
 import org.apache.archiva.event.Event;
 import org.apache.archiva.event.EventHandler;
-import org.apache.archiva.event.EventManager;
+import org.apache.archiva.event.BasicEventManager;
+import org.apache.archiva.event.EventSource;
 import org.apache.archiva.event.EventType;
+import org.apache.archiva.event.central.CentralEventManager;
 import org.apache.archiva.indexer.ArchivaIndexManager;
 import org.apache.archiva.indexer.ArchivaIndexingContext;
 import org.apache.archiva.indexer.IndexCreationFailedException;
@@ -65,6 +67,7 @@ import org.springframework.stereotype.Service;
 import javax.annotation.PostConstruct;
 import javax.annotation.PreDestroy;
 import javax.inject.Inject;
+import javax.inject.Named;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -111,9 +114,13 @@ public class ArchivaRepositoryRegistry implements ConfigurationListener, EventHa
     @Inject
     List<RepositoryValidator<? extends Repository>> repositoryValidatorList;
 
+    @Inject
+    @Named("eventManager#archiva")
+    CentralEventManager centralEventManager;
+
     private boolean ignoreIndexing = false;
 
-    private final EventManager eventManager;
+    private final BasicEventManager eventManager;
 
 
     private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock( );
@@ -133,7 +140,7 @@ public class ArchivaRepositoryRegistry implements ConfigurationListener, EventHa
 
     public ArchivaRepositoryRegistry( ConfigurationHandler configurationHandler, List<RepositoryValidator<? extends Repository>> validatorList )
     {
-        this.eventManager = new EventManager( this );
+        this.eventManager = new BasicEventManager( this );
         this.configurationHandler = configurationHandler;
         this.validators = initValidatorList( validatorList );
     }
@@ -172,6 +179,7 @@ public class ArchivaRepositoryRegistry implements ConfigurationListener, EventHa
                 provider.addRepositoryEventHandler( this );
             }
             this.configurationHandler.addListener( this );
+            registerEventHandler( EventType.ROOT, centralEventManager );
         }
         finally
         {
@@ -1162,15 +1170,15 @@ public class ArchivaRepositoryRegistry implements ConfigurationListener, EventHa
     @Override
     public void registerHandler( RepositoryHandler<?, ?> handler )
     {
-        if ( handler.getVariant( ).isAssignableFrom( RepositoryGroup.class ) )
+        if ( handler.getFlavour( ).isAssignableFrom( RepositoryGroup.class ) )
         {
             registerGroupHandler( (RepositoryHandler<RepositoryGroup, RepositoryGroupConfiguration>) handler );
         }
-        else if ( handler.getVariant( ).isAssignableFrom( ManagedRepository.class ) )
+        else if ( handler.getFlavour( ).isAssignableFrom( ManagedRepository.class ) )
         {
             registerManagedRepositoryHandler( (RepositoryHandler<ManagedRepository, ManagedRepositoryConfiguration>) handler );
         }
-        else if ( handler.getVariant().isAssignableFrom( RemoteRepository.class )) {
+        else if ( handler.getFlavour().isAssignableFrom( RemoteRepository.class )) {
             registerRemoteRepositoryHandler( (RepositoryHandler<RemoteRepository, RemoteRepositoryConfiguration>) handler );
         }
     }
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/managed/ManagedRepositoryHandler.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/managed/ManagedRepositoryHandler.java
index f7cf8a4..b46c12a 100644
--- a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/managed/ManagedRepositoryHandler.java
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/managed/ManagedRepositoryHandler.java
@@ -47,6 +47,8 @@ import org.springframework.stereotype.Service;
 import javax.annotation.PostConstruct;
 import javax.inject.Named;
 import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/base/group/RepositoryGroupHandlerTest.java b/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/base/group/RepositoryGroupHandlerTest.java
index fb607e4..be4933b 100644
--- a/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/base/group/RepositoryGroupHandlerTest.java
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/base/group/RepositoryGroupHandlerTest.java
@@ -148,7 +148,7 @@ class RepositoryGroupHandlerTest
 
     private RepositoryGroupHandler createHandler( )
     {
-        Mockito.when( managedRepositoryHandler.getVariant( ) ).thenReturn( ManagedRepository.class );
+        Mockito.when( managedRepositoryHandler.getFlavour( ) ).thenReturn( ManagedRepository.class );
         final ManagedRepository internalRepo;
         try
         {
@@ -161,7 +161,7 @@ class RepositoryGroupHandlerTest
         Mockito.when( managedRepositoryHandler.get( ArgumentMatchers.eq("internal") ) ).thenReturn( internalRepo );
         repositoryRegistry.registerHandler( managedRepositoryHandler );
 
-        Mockito.when( remoteRepositoryHandler.getVariant( ) ).thenReturn( RemoteRepository.class );
+        Mockito.when( remoteRepositoryHandler.getFlavour( ) ).thenReturn( RemoteRepository.class );
         final RemoteRepository centralRepo;
         try
         {
diff --git a/archiva-modules/archiva-base/pom.xml b/archiva-modules/archiva-base/pom.xml
index 55b0f87..91c0278 100644
--- a/archiva-modules/archiva-base/pom.xml
+++ b/archiva-modules/archiva-base/pom.xml
@@ -33,6 +33,7 @@
     <site.staging.base>${project.parent.basedir}</site.staging.base>
   </properties>
   <modules>
+    <module>archiva-event-api</module>
     <module>archiva-test-utils</module>
     <module>archiva-common</module>
     <module>archiva-mock</module>
@@ -53,5 +54,6 @@
     <module>archiva-security-common</module>
     <module>archiva-storage-api</module>
     <module>archiva-storage-fs</module>
+    <module>archiva-event-central</module>
   </modules>
 </project>
diff --git a/archiva-modules/archiva-maven/archiva-maven-repository/pom.xml b/archiva-modules/archiva-maven/archiva-maven-repository/pom.xml
index 7c042b5..1406524 100644
--- a/archiva-modules/archiva-maven/archiva-maven-repository/pom.xml
+++ b/archiva-modules/archiva-maven/archiva-maven-repository/pom.xml
@@ -33,6 +33,10 @@
 
   <dependencies>
     <dependency>
+      <groupId>org.apache.archiva.event</groupId>
+      <artifactId>archiva-event-api</artifactId>
+    </dependency>
+    <dependency>
       <groupId>org.apache.archiva</groupId>
       <artifactId>archiva-repository-api</artifactId>
     </dependency>
diff --git a/archiva-modules/archiva-maven/archiva-maven-repository/src/main/java/org/apache/archiva/maven/repository/MavenRepositoryProvider.java b/archiva-modules/archiva-maven/archiva-maven-repository/src/main/java/org/apache/archiva/maven/repository/MavenRepositoryProvider.java
index 8319e27..bb74096 100644
--- a/archiva-modules/archiva-maven/archiva-maven-repository/src/main/java/org/apache/archiva/maven/repository/MavenRepositoryProvider.java
+++ b/archiva-modules/archiva-maven/archiva-maven-repository/src/main/java/org/apache/archiva/maven/repository/MavenRepositoryProvider.java
@@ -118,6 +118,16 @@ public class MavenRepositoryProvider implements RepositoryProvider {
         return repo;
     }
 
+    private Path getBaseDir(String location) {
+        String lPathStr = location == null ? "" : location;
+        Path lPath = Paths.get( lPathStr );
+        if (lPath.isAbsolute()) {
+            return lPath.getParent( );
+        } else {
+            return archivaConfiguration.getRepositoryBaseDir( ).resolve( lPath );
+        }
+    }
+
     @Override
     public MavenRemoteRepository createRemoteInstance(String id, String name) {
         return createRemoteInstance(id, name, archivaConfiguration.getRemoteRepositoryBaseDir());
@@ -194,7 +204,7 @@ public class MavenRepositoryProvider implements RepositoryProvider {
 
     @Override
     public ManagedRepository createManagedInstance(ManagedRepositoryConfiguration cfg) throws RepositoryException {
-        MavenManagedRepository repo = createManagedInstance(cfg.getId(), cfg.getName(), Paths.get(cfg.getLocation()).getParent());
+        MavenManagedRepository repo = createManagedInstance( cfg.getId( ), cfg.getName( ), getBaseDir( cfg.getLocation( ) ) );
         updateManagedInstance(repo, cfg);
         return repo;
     }
diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/v2/model/MavenManagedRepository.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/v2/model/MavenManagedRepository.java
index 01ec552..d3ce968 100644
--- a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/v2/model/MavenManagedRepository.java
+++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/v2/model/MavenManagedRepository.java
@@ -73,37 +73,6 @@ public class MavenManagedRepository extends Repository
         super.setType( RepositoryType.MAVEN.name( ) );
     }
 
-    protected static void update(MavenManagedRepository repo, ManagedRepository beanRepo) {
-        repo.setDescription( beanRepo.getDescription() );
-        repo.setId( beanRepo.getId() );
-        repo.setIndex( true );
-        repo.setLayout( beanRepo.getLayout() );
-        repo.setBlocksRedeployments( beanRepo.blocksRedeployments() );
-        repo.setReleaseSchemes( beanRepo.getActiveReleaseSchemes().stream().map( Objects::toString).collect( Collectors.toList()) );
-        repo.setLocation( beanRepo.getLocation().toString() );
-        repo.setName( beanRepo.getName());
-        repo.setScanned( beanRepo.isScanned() );
-        repo.setSchedulingDefinition( beanRepo.getSchedulingDefinition() );
-        ArtifactCleanupFeature artifactCleanupFeature = beanRepo.getFeature( ArtifactCleanupFeature.class );
-        repo.setDeleteSnapshotsOfRelease( artifactCleanupFeature.isDeleteReleasedSnapshots());
-        repo.setRetentionCount( artifactCleanupFeature.getRetentionCount());
-        repo.setRetentionPeriod( artifactCleanupFeature.getRetentionPeriod() );
-        IndexCreationFeature icf = beanRepo.getFeature( IndexCreationFeature.class );
-        repo.setIndex( icf.hasIndex( ) );
-        repo.setIndexPath( icf.getIndexPath( ).getPath( ) );
-        repo.setPackedIndexPath( icf.getPackedIndexPath( ).getPath( ) );
-        repo.setSkipPackedIndexCreation( icf.isSkipPackedIndexCreation() );
-        StagingRepositoryFeature srf = beanRepo.getFeature( StagingRepositoryFeature.class );
-        repo.setHasStagingRepository( srf.isStageRepoNeeded( ) );
-        repo.setStagingRepository( srf.getStagingRepository()!=null?srf.getStagingRepository().getId():"" );
-    }
-
-    public static MavenManagedRepository of( ManagedRepository beanRepo ) {
-        MavenManagedRepository repo = new MavenManagedRepository( );
-        update( repo, beanRepo );
-        return repo;
-    }
-
     @Schema(name="blocks_redeployments",description = "True, if redeployments to this repository are not allowed")
     public boolean isBlocksRedeployments( )
     {
diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/v2/model/MavenManagedRepositoryUpdate.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/v2/model/MavenManagedRepositoryUpdate.java
index 5536ae9..1fcb1a0 100644
--- a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/v2/model/MavenManagedRepositoryUpdate.java
+++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/v2/model/MavenManagedRepositoryUpdate.java
@@ -17,6 +17,7 @@ package org.apache.archiva.rest.api.v2.model;
  * under the License.
  */
 
+import io.swagger.v3.oas.annotations.media.Schema;
 import org.apache.archiva.repository.ManagedRepository;
 
 import java.io.Serializable;
@@ -24,17 +25,13 @@ import java.io.Serializable;
 /**
  * @author Martin Stockhammer <ma...@apache.org>
  */
+@Schema(name="MavenManagedRepositoryUpdate",description = "Data object for updating maven managed repositories")
 public class MavenManagedRepositoryUpdate extends MavenManagedRepository implements Serializable
 {
     private static final long serialVersionUID = -9181643343284109862L;
     private boolean resetStats = false;
 
-    public static MavenManagedRepositoryUpdate of( ManagedRepository repository ) {
-        MavenManagedRepositoryUpdate repo = new MavenManagedRepositoryUpdate( );
-        update( repo, repository );
-        return repo;
-    }
-
+    @Schema(name="reset_stats",description = "True, if statistics should be reset after update")
     public boolean isResetStats( )
     {
         return resetStats;
diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/v2/model/map/MavenRepositoryMapper.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/v2/model/map/MavenRepositoryMapper.java
index 1e4ed68..75be523 100644
--- a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/v2/model/map/MavenRepositoryMapper.java
+++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/v2/model/map/MavenRepositoryMapper.java
@@ -64,14 +64,22 @@ public class MavenRepositoryMapper extends RestServiceMapper<MavenManagedReposit
         if (source.getLayout()!=null)
             target.setLayout( source.getLayout() );
         if (source.getLocation()!=null)
-            target.setLocation( source.getLocation() );
+        {
+            target.setLocation( source.getLocation( ) );
+        } else {
+            if (target.getLocation()==null) {
+                target.setLocation( "" );
+            }
+        }
+
         if (source.getPackedIndexPath()!=null)
             target.setPackedIndexDir( source.getPackedIndexPath() );
         if (source.getSchedulingDefinition()!=null)
             target.setRefreshCronExpression( source.getSchedulingDefinition() );
         target.setReleases( source.getReleaseSchemes( ).contains( ReleaseScheme.RELEASE.name() ) );
         target.setRetentionCount( source.getRetentionCount() );
-        target.setRetentionPeriod( source.getRetentionPeriod().getDays() );
+        if (source.getRetentionPeriod()!=null)
+            target.setRetentionPeriod( source.getRetentionPeriod().getDays() );
         target.setScanned( source.isScanned() );
         target.setSkipPackedIndexCreation( source.isSkipPackedIndexCreation() );
         target.setSnapshots( source.getReleaseSchemes( ).contains( ReleaseScheme.SNAPSHOT.name() ) );
diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/v2/svc/ErrorKeys.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/v2/svc/ErrorKeys.java
index cc0a836..57e4d40 100644
--- a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/v2/svc/ErrorKeys.java
+++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/v2/svc/ErrorKeys.java
@@ -122,4 +122,10 @@ public interface ErrorKeys
      * When the operation needs authentication, but not authenticated user was found in the request context.
      */
     String NOT_AUTHENTICATED = PREFIX + "user.not_authenticated";
+
+    /**
+     * Repository add action failed
+     */
+    String REPOSITORY_ADD_FAILED = PREFIX + "add.failed";
+    
 }
diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/test/java/org/apache/archiva/rest/api/v2/model/map/MavenRepositoryMapperTest.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/test/java/org/apache/archiva/rest/api/v2/model/map/MavenRepositoryMapperTest.java
index 0fd7770..3f0e04e 100644
--- a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/test/java/org/apache/archiva/rest/api/v2/model/map/MavenRepositoryMapperTest.java
+++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/test/java/org/apache/archiva/rest/api/v2/model/map/MavenRepositoryMapperTest.java
@@ -141,6 +141,51 @@ class MavenRepositoryMapperTest
     }
 
     @Test
+    void updateWithNullValues( )
+    {
+        MavenRepositoryMapper mapper = new MavenRepositoryMapper( );
+        MavenManagedRepository repo = new MavenManagedRepository( );
+        ManagedRepositoryConfiguration result = new ManagedRepositoryConfiguration( );
+        repo.setId( "repo01" );
+        repo.setName( "Repo 01" );
+        repo.setDescription( "This is repo 01" );
+        repo.setLocation( null );
+        repo.setHasStagingRepository( true );
+        repo.setSchedulingDefinition( "0,1,2 * * * *" );
+        repo.setPackedIndexPath( null );
+        repo.setIndexPath( null );
+        repo.setIndex( true );
+        repo.setDeleteSnapshotsOfRelease( false );
+        repo.setBlocksRedeployments( false );
+        repo.setReleaseSchemes( Arrays.asList( ReleaseScheme.RELEASE.name(), ReleaseScheme.SNAPSHOT.name() ) );
+        repo.setCharacteristic( Repository.CHARACTERISTIC_MANAGED );
+        repo.setScanned( true );
+        repo.setRetentionPeriod( null );
+        repo.setRetentionCount( 15 );
+        repo.setSkipPackedIndexCreation( false );
+        repo.setStagingRepository( null );
+        mapper.update( repo, result );
+
+        assertNotNull( result );
+        assertEquals( "repo01", result.getId( ) );
+        assertEquals( "Repo 01", result.getName( ) );
+        assertEquals( "This is repo 01", result.getDescription( ) );
+        assertNotNull( result.getLocation( ) );
+        assertTrue( result.isStageRepoNeeded( ) );
+        assertEquals( "0,1,2 * * * *", result.getRefreshCronExpression( ) );
+        assertEquals( "", result.getIndexDir( ) );
+        assertEquals( "", result.getPackedIndexDir( ) );
+        assertFalse( result.isDeleteReleasedSnapshots( ) );
+        assertFalse( result.isBlockRedeployments( ) );
+        assertTrue( result.isSnapshots( ) );
+        assertTrue( result.isReleases( ) );
+        assertTrue( result.isScanned( ) );
+        assertEquals( 100, result.getRetentionPeriod( ) );
+        assertEquals( 15, result.getRetentionCount( ) );
+        assertFalse( result.isSkipPackedIndexCreation( ) );
+    }
+
+    @Test
     void reverseMap( ) throws IOException, URISyntaxException, UnsupportedURIException
     {
         MavenRepositoryMapper mapper = new MavenRepositoryMapper( );
diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/pom.xml b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/pom.xml
index 7dd8dad..519fb1e 100644
--- a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/pom.xml
+++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/pom.xml
@@ -49,6 +49,10 @@
       <artifactId>archiva-storage-api</artifactId>
     </dependency>
     <dependency>
+      <groupId>org.apache.archiva.event</groupId>
+      <artifactId>archiva-event-api</artifactId>
+    </dependency>
+    <dependency>
       <groupId>org.apache.archiva</groupId>
       <artifactId>archiva-repository-admin-api</artifactId>
     </dependency>
diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/v2/svc/AbstractService.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/v2/svc/AbstractService.java
new file mode 100644
index 0000000..09e367e
--- /dev/null
+++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/v2/svc/AbstractService.java
@@ -0,0 +1,51 @@
+package org.apache.archiva.rest.v2.svc;
+/*
+ * 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.
+ */
+
+import org.apache.archiva.admin.model.AuditInformation;
+import org.apache.archiva.redback.rest.services.RedbackAuthenticationThreadLocal;
+import org.apache.archiva.redback.rest.services.RedbackRequestInformation;
+import org.apache.archiva.redback.users.User;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.core.Context;
+
+/**
+ * @author Martin Schreier <ma...@apache.org>
+ */
+public class AbstractService
+{
+    @Context
+    private HttpServletRequest httpServletRequest;
+
+    protected AuditInformation getAuditInformation( )
+    {
+        RedbackRequestInformation redbackRequestInformation = RedbackAuthenticationThreadLocal.get( );
+        User user;
+        String remoteAddr;
+        if (redbackRequestInformation==null) {
+            user = null;
+            remoteAddr = httpServletRequest.getRemoteAddr( );
+        } else
+        {
+            user = redbackRequestInformation.getUser( );
+            remoteAddr = redbackRequestInformation.getRemoteAddr( );
+        }
+        return new AuditInformation( user, remoteAddr );
+    }
+}
diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/v2/svc/maven/DefaultMavenManagedRepositoryService.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/v2/svc/maven/DefaultMavenManagedRepositoryService.java
index 5bec54d..406acff 100644
--- a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/v2/svc/maven/DefaultMavenManagedRepositoryService.java
+++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/v2/svc/maven/DefaultMavenManagedRepositoryService.java
@@ -20,13 +20,12 @@ package org.apache.archiva.rest.v2.svc.maven;
 import org.apache.archiva.admin.model.AuditInformation;
 import org.apache.archiva.admin.model.RepositoryAdminException;
 import org.apache.archiva.admin.model.managed.ManagedRepositoryAdmin;
+import org.apache.archiva.common.MultiModelMapper;
 import org.apache.archiva.components.rest.model.PagedResult;
 import org.apache.archiva.components.rest.util.QueryHelper;
 import org.apache.archiva.configuration.model.ManagedRepositoryConfiguration;
 import org.apache.archiva.redback.authentication.AuthenticationResult;
 import org.apache.archiva.redback.authorization.AuthorizationException;
-import org.apache.archiva.redback.rest.services.RedbackAuthenticationThreadLocal;
-import org.apache.archiva.redback.rest.services.RedbackRequestInformation;
 import org.apache.archiva.redback.system.DefaultSecuritySession;
 import org.apache.archiva.redback.system.SecuritySession;
 import org.apache.archiva.redback.system.SecuritySystem;
@@ -36,6 +35,7 @@ import org.apache.archiva.redback.users.UserNotFoundException;
 import org.apache.archiva.repository.ManagedRepository;
 import org.apache.archiva.repository.ReleaseScheme;
 import org.apache.archiva.repository.Repository;
+import org.apache.archiva.repository.RepositoryException;
 import org.apache.archiva.repository.RepositoryRegistry;
 import org.apache.archiva.repository.RepositoryType;
 import org.apache.archiva.repository.content.ContentItem;
@@ -44,10 +44,12 @@ import org.apache.archiva.repository.storage.fs.FsStorageUtil;
 import org.apache.archiva.rest.api.v2.model.FileInfo;
 import org.apache.archiva.rest.api.v2.model.MavenManagedRepository;
 import org.apache.archiva.rest.api.v2.model.MavenManagedRepositoryUpdate;
+import org.apache.archiva.rest.api.v2.model.map.ServiceMapperFactory;
 import org.apache.archiva.rest.api.v2.svc.ArchivaRestServiceException;
 import org.apache.archiva.rest.api.v2.svc.ErrorKeys;
 import org.apache.archiva.rest.api.v2.svc.ErrorMessage;
 import org.apache.archiva.rest.api.v2.svc.maven.MavenManagedRepositoryService;
+import org.apache.archiva.rest.v2.svc.AbstractService;
 import org.apache.archiva.security.common.ArchivaRoleConstants;
 import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
@@ -65,14 +67,14 @@ import java.util.List;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
-import static org.apache.archiva.security.common.ArchivaRoleConstants.OPERATION_READ_REPOSITORY;
 import static org.apache.archiva.security.common.ArchivaRoleConstants.OPERATION_ADD_ARTIFACT;
+import static org.apache.archiva.security.common.ArchivaRoleConstants.OPERATION_READ_REPOSITORY;
 
 /**
  * @author Martin Stockhammer <ma...@apache.org>
  */
 @Service("v2.managedMavenRepositoryService#rest")
-public class DefaultMavenManagedRepositoryService implements MavenManagedRepositoryService
+public class DefaultMavenManagedRepositoryService extends AbstractService implements MavenManagedRepositoryService
 {
     @Context
     HttpServletResponse httpServletResponse;
@@ -80,6 +82,8 @@ public class DefaultMavenManagedRepositoryService implements MavenManagedReposit
     @Context
     UriInfo uriInfo;
 
+
+
     private static final Logger log = LoggerFactory.getLogger( DefaultMavenManagedRepositoryService.class );
     private static final QueryHelper<ManagedRepository> QUERY_HELPER = new QueryHelper<>( new String[]{"id", "name"} );
     static
@@ -96,36 +100,20 @@ public class DefaultMavenManagedRepositoryService implements MavenManagedReposit
     private final ManagedRepositoryAdmin managedRepositoryAdmin;
     private final RepositoryRegistry repositoryRegistry;
     private final SecuritySystem securitySystem;
+    private final ServiceMapperFactory serviceMapperFactory;
+    private final MultiModelMapper<MavenManagedRepository, ManagedRepositoryConfiguration, ManagedRepository> mapper;
+
 
     public DefaultMavenManagedRepositoryService( SecuritySystem securitySystem,
                                                  RepositoryRegistry repositoryRegistry,
-                                                 ManagedRepositoryAdmin managedRepositoryAdmin )
+                                                 ManagedRepositoryAdmin managedRepositoryAdmin,
+                                                 ServiceMapperFactory serviceMapperFactory ) throws IllegalArgumentException
     {
         this.securitySystem = securitySystem;
         this.repositoryRegistry = repositoryRegistry;
         this.managedRepositoryAdmin = managedRepositoryAdmin;
-    }
-
-    protected AuditInformation getAuditInformation( )
-    {
-        RedbackRequestInformation redbackRequestInformation = RedbackAuthenticationThreadLocal.get( );
-        User user;
-        String remoteAddr;
-        if (redbackRequestInformation==null) {
-            user = null;
-            remoteAddr = null;
-        } else
-        {
-            user = redbackRequestInformation.getUser( );
-            remoteAddr = redbackRequestInformation.getRemoteAddr( );
-        }
-        return new AuditInformation( user, remoteAddr );
-    }
-
-    public static ManagedRepositoryConfiguration toConfig(MavenManagedRepository repo) {
-        ManagedRepositoryConfiguration cfg = new ManagedRepositoryConfiguration( );
-        return cfg;
-
+        this.serviceMapperFactory = serviceMapperFactory;
+        this.mapper = serviceMapperFactory.getMapper( MavenManagedRepository.class, ManagedRepositoryConfiguration.class, ManagedRepository.class );
     }
 
     @Override
@@ -140,7 +128,7 @@ public class DefaultMavenManagedRepositoryService implements MavenManagedReposit
             final Comparator<ManagedRepository> comparator = QUERY_HELPER.getComparator( orderBy, order );
             int totalCount = Math.toIntExact( repos.stream( ).filter( queryFilter ).count( ) );
             return PagedResult.of( totalCount, offset, limit, repos.stream( ).filter( queryFilter ).sorted( comparator )
-                .map( MavenManagedRepository::of ).skip( offset ).limit( limit ).collect( Collectors.toList( ) ) );
+                .map( mapper::reverseMap ).skip( offset ).limit( limit ).collect( Collectors.toList( ) ) );
         }
         catch (ArithmeticException e) {
             log.error( "Invalid number of repositories detected." );
@@ -158,7 +146,7 @@ public class DefaultMavenManagedRepositoryService implements MavenManagedReposit
         if (repo.getType()!=RepositoryType.MAVEN) {
             throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_WRONG_TYPE, repositoryId, repo.getType().name() ), 404 );
         }
-        return MavenManagedRepository.of( repo );
+        return mapper.reverseMap( repo );
     }
 
     @Override
@@ -220,13 +208,14 @@ public class DefaultMavenManagedRepositoryService implements MavenManagedReposit
         }
         try
         {
-            managedRepositoryAdmin.addManagedRepository( convert( managedRepository ), managedRepository.hasStagingRepository(), getAuditInformation() );
+            repositoryRegistry.putRepository( mapper.map( managedRepository ) );
             httpServletResponse.setStatus( 201 );
-            return MavenManagedRepository.of( repositoryRegistry.getManagedRepository( repoId ) );
+            return mapper.reverseMap( repositoryRegistry.getManagedRepository( repoId ) );
         }
-        catch ( RepositoryAdminException e )
+        catch ( RepositoryException e )
         {
-            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_ADMIN_ERROR, e.getMessage( ) ) );
+            log.error( "Could not create repository: {}", e.getMessage( ), e );
+            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_ADD_FAILED, repoId ) );
         }
     }
 
@@ -241,7 +230,7 @@ public class DefaultMavenManagedRepositoryService implements MavenManagedReposit
             if (newRepo==null) {
                 throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_UPDATE_FAILED, repositoryId ) );
             }
-            return MavenManagedRepository.of( newRepo );
+            return mapper.reverseMap( newRepo );
         }
         catch ( RepositoryAdminException e )
         {
diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/java/org/apache/archiva/rest/v2/svc/maven/NativeMavenManagedRepositoryServiceTest.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/java/org/apache/archiva/rest/v2/svc/maven/NativeMavenManagedRepositoryServiceTest.java
index 3dffabe..d012d61 100644
--- a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/java/org/apache/archiva/rest/v2/svc/maven/NativeMavenManagedRepositoryServiceTest.java
+++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/java/org/apache/archiva/rest/v2/svc/maven/NativeMavenManagedRepositoryServiceTest.java
@@ -100,7 +100,6 @@ public class NativeMavenManagedRepositoryServiceTest extends AbstractNativeRestS
     }
 
 
-    @Disabled
     @Test
     @Order( 2 )
     void testCreateRepository() {
@@ -111,7 +110,7 @@ public class NativeMavenManagedRepositoryServiceTest extends AbstractNativeRestS
         assertNotNull( json );
         assertEquals( "repo001", json.get( "id" ) );
         assertEquals( "Repository 001", json.get( "name" ) );
-        assertEquals( "maven", json.get( "type" ) );
+        assertEquals( "MAVEN", json.get( "type" ) );
         assertEquals( "This is repository 001", json.get( "description" ) );
     }
 
diff --git a/pom.xml b/pom.xml
index 4eaa746..ffcd4be 100644
--- a/pom.xml
+++ b/pom.xml
@@ -237,6 +237,16 @@
 
 
       <dependency>
+        <groupId>org.apache.archiva.event</groupId>
+        <artifactId>archiva-event-api</artifactId>
+        <version>${project.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.archiva.event</groupId>
+        <artifactId>archiva-event-central</artifactId>
+        <version>${project.version}</version>
+      </dependency>
+      <dependency>
         <groupId>org.apache.archiva.maven</groupId>
         <artifactId>archiva-maven-common</artifactId>
         <version>${project.version}</version>