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:56 UTC

[archiva] branch master updated (f73e942 -> 0a2dab1)

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

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


    from f73e942  Refactoring of repository feature interface
     new 99bd81a  Updating mapper for managed repo
     new 0a2dab1  Improving event API

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../archiva-consumer-archetype/pom.xml             |   6 +
 .../archiva-event-api}/pom.xml                     |  20 +-
 .../archiva/event/AbstractEventManager.java}       |  58 +++--
 .../apache/archiva/event/BasicEventManager.java}   |  33 ++-
 .../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 +-
 .../pom.xml                                        |  16 +-
 .../event/central/CentralEventManager.java}        |  26 +-
 .../src/main/resources/META-INF/spring-context.xml |  11 +-
 .../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    |  75 ++++--
 .../apache/archiva/rest/api/v2/svc/ErrorKeys.java  |   6 +
 .../v2/model/map/MavenRepositoryMapperTest.java    | 276 +++++++++++++++++++++
 .../archiva-rest/archiva-rest-services/pom.xml     |   4 +
 .../svc/AbstractService.java}                      |  25 +-
 .../DefaultMavenManagedRepositoryService.java      |  57 ++---
 .../NativeMavenManagedRepositoryServiceTest.java   |   3 +-
 pom.xml                                            |  10 +
 41 files changed, 1037 insertions(+), 211 deletions(-)
 copy archiva-modules/{metadata/metadata-store-provider => archiva-base/archiva-event-api}/pom.xml (77%)
 rename archiva-modules/archiva-base/{archiva-common/src/main/java/org/apache/archiva/event/EventManager.java => archiva-event-api/src/main/java/org/apache/archiva/event/AbstractEventManager.java} (50%)
 copy archiva-modules/{archiva-web/archiva-webapp/src/main/archiva-web/src/app/model/pagination-info.ts => archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/BasicEventManager.java} (59%)
 rename archiva-modules/archiva-base/{archiva-common => archiva-event-api}/src/main/java/org/apache/archiva/event/Event.java (67%)
 create mode 100644 archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/EventContext.java
 create mode 100644 archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/EventContextBuilder.java
 rename archiva-modules/archiva-base/{archiva-common => archiva-event-api}/src/main/java/org/apache/archiva/event/EventHandler.java (100%)
 rename archiva-modules/archiva-base/{archiva-common => archiva-event-api}/src/main/java/org/apache/archiva/event/EventSource.java (100%)
 rename archiva-modules/archiva-base/{archiva-common => archiva-event-api}/src/main/java/org/apache/archiva/event/EventType.java (93%)
 create mode 100644 archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/context/RepositoryContext.java
 create mode 100644 archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/context/RestContext.java
 create mode 100644 archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/context/UserContext.java
 create mode 100644 archiva-modules/archiva-base/archiva-event-api/src/main/java/org/apache/archiva/event/package-info.java
 rename archiva-modules/archiva-base/{archiva-common/src/test/java/org/apache/archiva/event/EventManagerTest.java => archiva-event-api/src/test/java/org/apache/archiva/event/BasicEventManagerTest.java} (94%)
 copy archiva-modules/archiva-base/{archiva-configuration/archiva-configuration-model => archiva-event-central}/pom.xml (75%)
 copy archiva-modules/{archiva-maven/archiva-maven-scheduler/src/main/java/org/apache/archiva/maven/scheduler/indexing/DefaultIndexUpdateSideEffect.java => archiva-base/archiva-event-central/src/main/java/org/apache/archiva/event/central/CentralEventManager.java} (55%)
 copy archiva-modules/{archiva-web/archiva-webdav => archiva-base/archiva-event-central}/src/main/resources/META-INF/spring-context.xml (87%)
 create mode 100644 archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/test/java/org/apache/archiva/rest/api/v2/model/map/MavenRepositoryMapperTest.java
 copy archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/{services/utils/AuditHelper.java => v2/svc/AbstractService.java} (66%)

[archiva] 01/02: Updating mapper for managed repo

Posted by ma...@apache.org.
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 99bd81ac000598105529473a53f62a5b53d885a5
Author: Martin Schreier <ma...@apache.org>
AuthorDate: Thu Jan 13 21:00:33 2022 +0100

    Updating mapper for managed repo
---
 .../api/v2/model/map/MavenRepositoryMapper.java    |  65 ++++--
 .../v2/model/map/MavenRepositoryMapperTest.java    | 231 +++++++++++++++++++++
 2 files changed, 279 insertions(+), 17 deletions(-)

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 d9f00fc..1e4ed68 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
@@ -22,6 +22,7 @@ import org.apache.archiva.repository.ManagedRepository;
 import org.apache.archiva.repository.ReleaseScheme;
 import org.apache.archiva.repository.RepositoryType;
 import org.apache.archiva.repository.features.ArtifactCleanupFeature;
+import org.apache.archiva.repository.features.IndexCreationFeature;
 import org.apache.archiva.repository.features.StagingRepositoryFeature;
 import org.apache.archiva.rest.api.v2.model.MavenManagedRepository;
 import org.springframework.stereotype.Service;
@@ -48,24 +49,32 @@ public class MavenRepositoryMapper extends RestServiceMapper<MavenManagedReposit
     @Override
     public void update( MavenManagedRepository source, ManagedRepositoryConfiguration target )
     {
-        target.setId( source.getId() );
-        target.setName( source.getName() );
-        target.setDescription( source.getDescription( ) );
+        if (source.getId()!=null)
+            target.setId( source.getId() );
+        if (source.getName()!=null)
+            target.setName( source.getName() );
+        if (source.getDescription()!=null)
+            target.setDescription( source.getDescription( ) );
         target.setType( TYPE );
 
         target.setBlockRedeployments( source.isBlocksRedeployments() );
         target.setDeleteReleasedSnapshots( source.isDeleteSnapshotsOfRelease() );
-        target.setIndexDir( source.getIndexPath() );
-        target.setLayout( source.getLayout() );
-        target.setLocation( source.getLocation() );
-        target.setPackedIndexDir( source.getPackedIndexPath() );
-        target.setRefreshCronExpression( source.getSchedulingDefinition() );
-        target.setReleases( source.getReleaseSchemes( ).contains( ReleaseScheme.RELEASE ) );
+        if (source.getIndexPath()!=null)
+            target.setIndexDir( source.getIndexPath() );
+        if (source.getLayout()!=null)
+            target.setLayout( source.getLayout() );
+        if (source.getLocation()!=null)
+            target.setLocation( source.getLocation() );
+        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() );
         target.setScanned( source.isScanned() );
         target.setSkipPackedIndexCreation( source.isSkipPackedIndexCreation() );
-        target.setSnapshots( source.getReleaseSchemes( ).contains( ReleaseScheme.SNAPSHOT ) );
+        target.setSnapshots( source.getReleaseSchemes( ).contains( ReleaseScheme.SNAPSHOT.name() ) );
         target.setStageRepoNeeded( source.hasStagingRepository() );
 
     }
@@ -74,18 +83,40 @@ public class MavenRepositoryMapper extends RestServiceMapper<MavenManagedReposit
     public MavenManagedRepository reverseMap( ManagedRepository source )
     {
         MavenManagedRepository result = new MavenManagedRepository( );
-        StagingRepositoryFeature srf = source.getFeature( StagingRepositoryFeature.class );
-        ArtifactCleanupFeature acf = source.getFeature( ArtifactCleanupFeature.class );
-        result.setHasStagingRepository( srf.isStageRepoNeeded() );
-        result.setBlocksRedeployments( source.blocksRedeployments() );
-        result.setIndex( source.hasIndex() );
-        result.setStagingRepository( srf.getStagingRepository().getId() );
-        return null;
+        reverseUpdate( source, result );
+        return result;
     }
 
     @Override
     public void reverseUpdate( ManagedRepository source, MavenManagedRepository target )
     {
+        StagingRepositoryFeature srf = source.getFeature( StagingRepositoryFeature.class );
+        ArtifactCleanupFeature acf = source.getFeature( ArtifactCleanupFeature.class );
+        IndexCreationFeature icf = source.getFeature( IndexCreationFeature.class );
+
+
+        target.setId( source.getId( ) );
+        target.setName( source.getName( ) );
+        target.setDescription( source.getDescription() );
+
+        target.setBlocksRedeployments( source.blocksRedeployments() );
+        target.setDeleteSnapshotsOfRelease( acf.isDeleteReleasedSnapshots() );
+        target.setIndex( source.hasIndex() );
+        target.setIndexPath( icf.getIndexPath().toString() );
+        target.setLayout( source.getLayout() );
+        target.setLocation( source.getLocation().toString() );
+        target.setPackedIndexPath( icf.getPackedIndexPath().toString() );
+        target.setSchedulingDefinition( source.getSchedulingDefinition() );
+        for ( ReleaseScheme scheme: source.getActiveReleaseSchemes() ) {
+            target.addReleaseScheme( scheme.toString() );
+        }
+        target.setRetentionCount( acf.getRetentionCount() );
+        target.setRetentionPeriod( acf.getRetentionPeriod() );
+        target.setScanned( source.isScanned() );
+        target.setSkipPackedIndexCreation( icf.isSkipPackedIndexCreation() );
+        if (srf.getStagingRepository()!=null)
+            target.setStagingRepository( srf.getStagingRepository().getId() );
+        target.setHasStagingRepository( srf.isStageRepoNeeded() );
 
     }
 
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
new file mode 100644
index 0000000..0fd7770
--- /dev/null
+++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/test/java/org/apache/archiva/rest/api/v2/model/map/MavenRepositoryMapperTest.java
@@ -0,0 +1,231 @@
+package org.apache.archiva.rest.api.v2.model.map;
+
+/*
+ * 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.common.filelock.DefaultFileLockManager;
+import org.apache.archiva.configuration.model.ManagedRepositoryConfiguration;
+import org.apache.archiva.repository.EditableManagedRepository;
+import org.apache.archiva.repository.ReleaseScheme;
+import org.apache.archiva.repository.RepositoryType;
+import org.apache.archiva.repository.UnsupportedURIException;
+import org.apache.archiva.repository.base.managed.BasicManagedRepository;
+import org.apache.archiva.repository.features.ArtifactCleanupFeature;
+import org.apache.archiva.repository.features.IndexCreationFeature;
+import org.apache.archiva.repository.features.StagingRepositoryFeature;
+import org.apache.archiva.repository.storage.fs.FilesystemStorage;
+import org.apache.archiva.rest.api.v2.model.MavenManagedRepository;
+import org.apache.archiva.rest.api.v2.model.Repository;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Period;
+import java.util.Arrays;
+import java.util.Locale;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * @author Martin Schreier <ma...@apache.org>
+ */
+class MavenRepositoryMapperTest
+{
+
+    @Test
+    void map( )
+    {
+        MavenRepositoryMapper mapper = new MavenRepositoryMapper( );
+        MavenManagedRepository repo = new MavenManagedRepository( );
+        repo.setId( "repo01" );
+        repo.setName( "Repo 01" );
+        repo.setDescription( "This is repo 01" );
+        repo.setLocation( "/data/repo01" );
+        repo.setHasStagingRepository( true );
+        repo.setSchedulingDefinition( "0,1,2 * * * *" );
+        repo.setPackedIndexPath( ".index" );
+        repo.setIndexPath( ".indexer" );
+        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( Period.ofDays( 10 ) );
+        repo.setRetentionCount( 15 );
+        repo.setSkipPackedIndexCreation( false );
+        repo.setStagingRepository( "stage-repo01" );
+        ManagedRepositoryConfiguration result = mapper.map( repo );
+
+        assertNotNull( result );
+        assertEquals( "repo01", result.getId( ) );
+        assertEquals( "Repo 01", result.getName( ) );
+        assertEquals( "This is repo 01", result.getDescription( ) );
+        assertEquals( "/data/repo01", result.getLocation( ) );
+        assertTrue( result.isStageRepoNeeded( ) );
+        assertEquals( "0,1,2 * * * *", result.getRefreshCronExpression( ) );
+        assertEquals( ".indexer", result.getIndexDir( ) );
+        assertEquals( ".index", result.getPackedIndexDir( ) );
+        assertFalse( result.isDeleteReleasedSnapshots( ) );
+        assertFalse( result.isBlockRedeployments( ) );
+        assertTrue( result.isSnapshots( ) );
+        assertTrue( result.isReleases( ) );
+        assertTrue( result.isScanned( ) );
+        assertEquals( 10, result.getRetentionPeriod( ) );
+        assertEquals( 15, result.getRetentionCount( ) );
+        assertFalse( result.isSkipPackedIndexCreation( ) );
+
+    }
+
+    @Test
+    void update( )
+    {
+        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( "/data/repo01" );
+        repo.setHasStagingRepository( true );
+        repo.setSchedulingDefinition( "0,1,2 * * * *" );
+        repo.setPackedIndexPath( ".index" );
+        repo.setIndexPath( ".indexer" );
+        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( Period.ofDays( 10 ) );
+        repo.setRetentionCount( 15 );
+        repo.setSkipPackedIndexCreation( false );
+        repo.setStagingRepository( "stage-repo01" );
+         mapper.update( repo, result );
+
+        assertNotNull( result );
+        assertEquals( "repo01", result.getId( ) );
+        assertEquals( "Repo 01", result.getName( ) );
+        assertEquals( "This is repo 01", result.getDescription( ) );
+        assertEquals( "/data/repo01", result.getLocation( ) );
+        assertTrue( result.isStageRepoNeeded( ) );
+        assertEquals( "0,1,2 * * * *", result.getRefreshCronExpression( ) );
+        assertEquals( ".indexer", result.getIndexDir( ) );
+        assertEquals( ".index", result.getPackedIndexDir( ) );
+        assertFalse( result.isDeleteReleasedSnapshots( ) );
+        assertFalse( result.isBlockRedeployments( ) );
+        assertTrue( result.isSnapshots( ) );
+        assertTrue( result.isReleases( ) );
+        assertTrue( result.isScanned( ) );
+        assertEquals( 10, result.getRetentionPeriod( ) );
+        assertEquals( 15, result.getRetentionCount( ) );
+        assertFalse( result.isSkipPackedIndexCreation( ) );
+    }
+
+    @Test
+    void reverseMap( ) throws IOException, URISyntaxException, UnsupportedURIException
+    {
+        MavenRepositoryMapper mapper = new MavenRepositoryMapper( );
+
+        Path tmpDir = Files.createTempDirectory( "mapper-test" );
+        FilesystemStorage fsStorage = new FilesystemStorage( tmpDir, new DefaultFileLockManager( ) );
+        EditableManagedRepository repository = new BasicManagedRepository( Locale.getDefault(), RepositoryType.MAVEN, "repo02", "Repo 02", fsStorage );
+        repository.setDescription( Locale.getDefault(), "This is repo 02" );
+        repository.setBlocksRedeployment( false );
+        repository.setLocation( new URI("test-path") );
+        repository.setScanned( true );
+        repository.setLayout( "maven2" );
+        repository.setSchedulingDefinition( "* 3,5,10 * * *" );
+        IndexCreationFeature icf = repository.getFeature( IndexCreationFeature.class );
+        icf.setIndexPath( new URI( ".indexer" ) );
+        icf.setPackedIndexPath( new URI( ".index" ) );
+        icf.setSkipPackedIndexCreation( false );
+        ArtifactCleanupFeature acf = repository.getFeature( ArtifactCleanupFeature.class );
+        acf.setDeleteReleasedSnapshots( false );
+        acf.setRetentionPeriod( Period.ofDays( 5 ) );
+        acf.setRetentionCount( 17 );
+        StagingRepositoryFeature srf = repository.getFeature( StagingRepositoryFeature.class );
+        srf.setStageRepoNeeded( false );
+
+        MavenManagedRepository result = mapper.reverseMap( repository );
+        assertEquals( "repo02", result.getId( ) );
+        assertEquals( "Repo 02", result.getName( ) );
+        assertEquals( "This is repo 02", result.getDescription( ) );
+        assertFalse( result.isBlocksRedeployments( ) );
+        assertEquals( "test-path", result.getLocation( ) );
+        assertTrue( result.isScanned( ) );
+        assertEquals( "maven2", result.getLayout( ) );
+        assertEquals( "* 3,5,10 * * *", result.getSchedulingDefinition( ) );
+        assertEquals( ".indexer", result.getIndexPath( ) );
+        assertEquals( ".index", result.getPackedIndexPath( ) );
+        assertFalse( result.isSkipPackedIndexCreation( ) );
+        assertFalse( result.isDeleteSnapshotsOfRelease( ) );
+        assertEquals( Period.ofDays( 5 ), result.getRetentionPeriod( ) );
+        assertEquals( 17, result.getRetentionCount( ) );
+        assertFalse( result.hasStagingRepository( ) );
+
+    }
+
+    @Test
+    void reverseUpdate( ) throws IOException, URISyntaxException, UnsupportedURIException
+    {
+        MavenRepositoryMapper mapper = new MavenRepositoryMapper( );
+        MavenManagedRepository result = new MavenManagedRepository( );
+        Path tmpDir = Files.createTempDirectory( "mapper-test" );
+        FilesystemStorage fsStorage = new FilesystemStorage( tmpDir, new DefaultFileLockManager( ) );
+        EditableManagedRepository repository = new BasicManagedRepository( Locale.getDefault(), RepositoryType.MAVEN, "repo02", "Repo 02", fsStorage );
+        repository.setDescription( Locale.getDefault(), "This is repo 02" );
+        repository.setBlocksRedeployment( false );
+        repository.setLocation( new URI("test-path") );
+        repository.setScanned( true );
+        repository.setLayout( "maven2" );
+        repository.setSchedulingDefinition( "* 3,5,10 * * *" );
+        IndexCreationFeature icf = repository.getFeature( IndexCreationFeature.class );
+        icf.setIndexPath( new URI( ".indexer" ) );
+        icf.setPackedIndexPath( new URI( ".index" ) );
+        icf.setSkipPackedIndexCreation( false );
+        ArtifactCleanupFeature acf = repository.getFeature( ArtifactCleanupFeature.class );
+        acf.setDeleteReleasedSnapshots( false );
+        acf.setRetentionPeriod( Period.ofDays( 5 ) );
+        acf.setRetentionCount( 17 );
+        StagingRepositoryFeature srf = repository.getFeature( StagingRepositoryFeature.class );
+        srf.setStageRepoNeeded( false );
+
+        mapper.reverseUpdate( repository, result );
+
+        assertEquals( "repo02", result.getId( ) );
+        assertEquals( "Repo 02", result.getName( ) );
+        assertEquals( "This is repo 02", result.getDescription( ) );
+        assertFalse( result.isBlocksRedeployments( ) );
+        assertEquals( "test-path", result.getLocation( ) );
+        assertTrue( result.isScanned( ) );
+        assertEquals( "maven2", result.getLayout( ) );
+        assertEquals( "* 3,5,10 * * *", result.getSchedulingDefinition( ) );
+        assertEquals( ".indexer", result.getIndexPath( ) );
+        assertEquals( ".index", result.getPackedIndexPath( ) );
+        assertFalse( result.isSkipPackedIndexCreation( ) );
+        assertFalse( result.isDeleteSnapshotsOfRelease( ) );
+        assertEquals( Period.ofDays( 5 ), result.getRetentionPeriod( ) );
+        assertEquals( 17, result.getRetentionCount( ) );
+        assertFalse( result.hasStagingRepository( ) );
+
+    }
+}
\ No newline at end of file

[archiva] 02/02: Improving event API

Posted by ma...@apache.org.
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>