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


The following commit(s) were added to refs/heads/master by this push:
     new 0ce5348  Centralizing repository group handling. First part.
0ce5348 is described below

commit 0ce53483a85b6b7a3a14417434edf56934b474ba
Author: Martin Stockhammer <[email protected]>
AuthorDate: Tue Jun 1 22:46:51 2021 +0200

    Centralizing repository group handling. First part.
---
 .../configuration/ArchivaConfiguration.java        |  14 +
 .../archiva/configuration/ConfigurationEvent.java  |  54 ++--
 .../configuration/DefaultArchivaConfiguration.java |  23 +-
 .../repository/AbstractRepositoryPurgeTest.java    |   5 +
 ...leanupReleasedSnapshotsRepositoryPurgeTest.java |   2 +
 .../consumers/lucene/NexusIndexerConsumerTest.java |   4 +
 .../group/DefaultRepositoryGroupAdmin.java         |  20 +-
 .../repository/AbstractRepositoryAdminTest.java    |   5 +
 .../repository/group/RepositoryGroupAdminTest.java |   4 +
 .../repository/event/RepositoryRegistryEvent.java  |  15 +
 .../repository/base/ArchivaRepositoryRegistry.java | 275 +++++------------
 .../repository/base/ConfigurationHandler.java      |  71 +++++
 .../repository/base/RepositoryGroupHandler.java    | 328 +++++++++++++++++++++
 .../base/ArchivaRepositoryRegistryTest.java        |   4 +
 .../indexer/maven/MavenIndexManagerTest.java       |   4 +
 .../search/AbstractMavenRepositorySearch.java      |  11 +-
 .../search/MavenRepositorySearchOSGITest.java      |   4 +
 .../search/MavenRepositorySearchPaginateTest.java  |   7 +-
 .../apache/archiva/proxy/MockConfiguration.java    |   6 +
 .../metadata/storage/mock/MockConfiguration.java   |   7 +
 .../maven/mock/RepositoryRegistryMock.java         |   6 +
 .../mock/configuration/StubConfiguration.java      |   6 +
 .../mock/configuration/TestConfiguration.java      |   6 +
 .../maven/ArchivaIndexingTaskExecutorTest.java     |   4 +
 ...tArchivaRepositoryScanningTaskExecutorTest.java |   4 +
 .../api/services/v2/RepositoryGroupService.java    |  92 +++---
 .../{ => maven}/MavenManagedRepositoryService.java |   6 +-
 .../rest/services/DefaultRepositoriesService.java  |   5 +
 .../archiva/rest/services/utils/AuditHelper.java   |  40 +++
 .../v2/DefaultMavenManagedRepositoryService.java   |  25 +-
 .../services/v2/DefaultRepositoryGroupService.java |  28 +-
 .../src/main/resources/META-INF/spring-context.xml |   8 +-
 .../services/v2/AbstractNativeRestServices.java    |  16 +-
 .../NativeMavenManagedRepositoryServiceTest.java   |  97 ++++++
 .../resources/META-INF/spring-context-test.xml     |   2 +-
 .../webdav/AbstractRepositoryServletTestCase.java  |   4 +
 .../webdav/ArchivaDavResourceFactoryTest.java      |  10 +-
 .../webdav/RepositoryServletSecurityTest.java      |   5 +
 38 files changed, 897 insertions(+), 330 deletions(-)

diff --git 
a/archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/archiva/configuration/ArchivaConfiguration.java
 
b/archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/archiva/configuration/ArchivaConfiguration.java
index 9d8ead0..f230257 100644
--- 
a/archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/archiva/configuration/ArchivaConfiguration.java
+++ 
b/archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/archiva/configuration/ArchivaConfiguration.java
@@ -56,6 +56,20 @@ public interface ArchivaConfiguration
         throws RegistryException, IndeterminateConfigurationException;
 
     /**
+     * Save any updated configuration. This method allows to add a tag to the 
thrown event.
+     * This allows to verify the origin if the caller is the same as the 
listener.
+     *
+     * @param configuration the configuration to save
+     * @param eventTag the tag to add to the thrown event
+     * @throws org.apache.archiva.components.registry.RegistryException
+     *          if there is a problem saving the registry data
+     * @throws IndeterminateConfigurationException
+     *          if the configuration cannot be saved because it was read from 
two sources
+     */
+    void save( Configuration configuration, String eventTag )
+        throws RegistryException, IndeterminateConfigurationException;
+
+    /**
      * Determines if the configuration in use was as a result of a defaulted 
configuration.
      *
      * @return true if the configuration was created from the 
default-archiva.xml as opposed
diff --git 
a/archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/archiva/configuration/ConfigurationEvent.java
 
b/archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/archiva/configuration/ConfigurationEvent.java
index 397c838..9639eff 100644
--- 
a/archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/archiva/configuration/ConfigurationEvent.java
+++ 
b/archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/archiva/configuration/ConfigurationEvent.java
@@ -32,9 +32,17 @@ public class ConfigurationEvent
 
     private int type;
 
+    private String tag;
+
     public ConfigurationEvent( int type )
     {
         this.type = type;
+        tag = "";
+    }
+
+    public ConfigurationEvent(int type, String tag) {
+        this.type = type;
+        this.tag = tag;
     }
 
     public int getType()
@@ -42,35 +50,33 @@ public class ConfigurationEvent
         return type;
     }
 
+    public String getTag( )
+    {
+        return tag;
+    }
+
+    public void setTag( String tag )
+    {
+        this.tag = tag;
+    }
+
     @Override
-    public int hashCode()
+    public boolean equals( Object o )
     {
-        final int prime = 31;
-        int result = 1;
-        result = prime * result + type;
-        return result;
+        if ( this == o ) return true;
+        if ( o == null || getClass( ) != o.getClass( ) ) return false;
+
+        ConfigurationEvent that = (ConfigurationEvent) o;
+
+        if ( type != that.type ) return false;
+        return tag.equals( that.tag );
     }
 
     @Override
-    public boolean equals( Object obj )
+    public int hashCode( )
     {
-        if ( this == obj )
-        {
-            return true;
-        }
-        if ( obj == null )
-        {
-            return false;
-        }
-        if ( getClass() != obj.getClass() )
-        {
-            return false;
-        }
-        final ConfigurationEvent other = (ConfigurationEvent) obj;
-        if ( type != other.type )
-        {
-            return false;
-        }
-        return true;
+        int result = type;
+        result = 31 * result + tag.hashCode( );
+        return result;
     }
 }
diff --git 
a/archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/archiva/configuration/DefaultArchivaConfiguration.java
 
b/archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/archiva/configuration/DefaultArchivaConfiguration.java
index 11ae705..ee80fc8 100644
--- 
a/archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/archiva/configuration/DefaultArchivaConfiguration.java
+++ 
b/archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/archiva/configuration/DefaultArchivaConfiguration.java
@@ -410,7 +410,20 @@ public class DefaultArchivaConfiguration
 
     @SuppressWarnings("unchecked")
     @Override
-    public synchronized void save(Configuration configuration)
+    public synchronized void save(Configuration configuration) throws 
IndeterminateConfigurationException, RegistryException
+    {
+        save( configuration, "" );
+    }
+
+    /**
+     * Saves the configuration and adds the given tag to the event.
+     * @param configuration the configuration to save
+     * @param eventTag the tag to add to the configuration saved event
+     * @throws IndeterminateConfigurationException if the
+     * @throws RegistryException
+     */
+    @Override
+    public synchronized void save(Configuration configuration, String eventTag)
             throws IndeterminateConfigurationException, RegistryException {
         Registry section = registry.getSection(KEY + ".user");
         Registry baseSection = registry.getSection(KEY + ".base");
@@ -491,7 +504,7 @@ public class DefaultArchivaConfiguration
         this.configuration = unescapeExpressions(configuration);
         isConfigurationDefaulted = false;
 
-        triggerEvent(ConfigurationEvent.SAVED);
+        triggerEvent(ConfigurationEvent.SAVED, eventTag);
     }
 
     private void escapeCronExpressions(Configuration configuration) {
@@ -530,7 +543,7 @@ public class DefaultArchivaConfiguration
             addRegistryChangeListener(regListener);
         }
 
-        triggerEvent(ConfigurationEvent.SAVED);
+        triggerEvent(ConfigurationEvent.SAVED, "default-file");
 
         Registry section = registry.getSection(KEY + ".user");
         if (section == null) {
@@ -580,8 +593,8 @@ public class DefaultArchivaConfiguration
         }
     }
 
-    private void triggerEvent(int type) {
-        ConfigurationEvent evt = new ConfigurationEvent(type);
+    private void triggerEvent(int type, String eventTag) {
+        ConfigurationEvent evt = new ConfigurationEvent(type, eventTag);
         for (ConfigurationListener listener : listeners) {
             listener.configurationEvent(evt);
         }
diff --git 
a/archiva-modules/archiva-base/archiva-consumers/archiva-core-consumers/src/test/java/org/apache/archiva/consumers/core/repository/AbstractRepositoryPurgeTest.java
 
b/archiva-modules/archiva-base/archiva-consumers/archiva-core-consumers/src/test/java/org/apache/archiva/consumers/core/repository/AbstractRepositoryPurgeTest.java
index f1791b7..b2f66aa 100644
--- 
a/archiva-modules/archiva-base/archiva-consumers/archiva-core-consumers/src/test/java/org/apache/archiva/consumers/core/repository/AbstractRepositoryPurgeTest.java
+++ 
b/archiva-modules/archiva-base/archiva-consumers/archiva-core-consumers/src/test/java/org/apache/archiva/consumers/core/repository/AbstractRepositoryPurgeTest.java
@@ -26,6 +26,7 @@ import 
org.apache.archiva.metadata.repository.RepositorySessionFactory;
 import org.apache.archiva.repository.ManagedRepositoryContent;
 import org.apache.archiva.repository.RepositoryRegistry;
 import org.apache.archiva.repository.base.ArchivaRepositoryRegistry;
+import org.apache.archiva.repository.base.RepositoryGroupHandler;
 import 
org.apache.archiva.repository.maven.metadata.storage.Maven2RepositoryPathTranslator;
 import org.apache.archiva.repository.base.BasicManagedRepository;
 import org.apache.archiva.repository.ReleaseScheme;
@@ -113,6 +114,10 @@ public abstract class AbstractRepositoryPurgeTest
     @Inject
     protected ApplicationContext applicationContext;
 
+    @SuppressWarnings( "unused" )
+    @Inject
+    RepositoryGroupHandler repositoryGroupHandler;
+
 
     @Before
     public void setUp()
diff --git 
a/archiva-modules/archiva-base/archiva-consumers/archiva-core-consumers/src/test/java/org/apache/archiva/consumers/core/repository/CleanupReleasedSnapshotsRepositoryPurgeTest.java
 
b/archiva-modules/archiva-base/archiva-consumers/archiva-core-consumers/src/test/java/org/apache/archiva/consumers/core/repository/CleanupReleasedSnapshotsRepositoryPurgeTest.java
index 76b6ce7..8e497dc 100644
--- 
a/archiva-modules/archiva-base/archiva-consumers/archiva-core-consumers/src/test/java/org/apache/archiva/consumers/core/repository/CleanupReleasedSnapshotsRepositoryPurgeTest.java
+++ 
b/archiva-modules/archiva-base/archiva-consumers/archiva-core-consumers/src/test/java/org/apache/archiva/consumers/core/repository/CleanupReleasedSnapshotsRepositoryPurgeTest.java
@@ -74,6 +74,8 @@ public class CleanupReleasedSnapshotsRepositoryPurgeTest
     @Inject
     MetadataTools metadataTools;
 
+
+
     @Before
     @Override
     public void setUp()
diff --git 
a/archiva-modules/archiva-base/archiva-consumers/archiva-indexer-consumers/src/test/java/org/apache/archiva/consumers/lucene/NexusIndexerConsumerTest.java
 
b/archiva-modules/archiva-base/archiva-consumers/archiva-indexer-consumers/src/test/java/org/apache/archiva/consumers/lucene/NexusIndexerConsumerTest.java
index 85ee942..241eeb0 100644
--- 
a/archiva-modules/archiva-base/archiva-consumers/archiva-indexer-consumers/src/test/java/org/apache/archiva/consumers/lucene/NexusIndexerConsumerTest.java
+++ 
b/archiva-modules/archiva-base/archiva-consumers/archiva-indexer-consumers/src/test/java/org/apache/archiva/consumers/lucene/NexusIndexerConsumerTest.java
@@ -27,6 +27,7 @@ import 
org.apache.archiva.components.taskqueue.TaskQueueException;
 import org.apache.archiva.repository.base.ArchivaRepositoryRegistry;
 import org.apache.archiva.repository.base.BasicManagedRepository;
 import org.apache.archiva.repository.ReleaseScheme;
+import org.apache.archiva.repository.base.RepositoryGroupHandler;
 import org.apache.archiva.scheduler.ArchivaTaskScheduler;
 import org.apache.archiva.scheduler.indexing.ArtifactIndexingTask;
 import org.apache.archiva.test.utils.ArchivaSpringJUnit4ClassRunner;
@@ -96,6 +97,9 @@ public class NexusIndexerConsumerTest
     @Inject
     ArchivaRepositoryRegistry repositoryRegistry;
 
+    @SuppressWarnings( "unused" )
+    @Inject
+    RepositoryGroupHandler repositoryGroupHandler;
 
     @Override
     @Before
diff --git 
a/archiva-modules/archiva-base/archiva-repository-admin/archiva-repository-admin-default/src/main/java/org/apache/archiva/admin/repository/group/DefaultRepositoryGroupAdmin.java
 
b/archiva-modules/archiva-base/archiva-repository-admin/archiva-repository-admin-default/src/main/java/org/apache/archiva/admin/repository/group/DefaultRepositoryGroupAdmin.java
index ae6c4fd..13f3ce6 100644
--- 
a/archiva-modules/archiva-base/archiva-repository-admin/archiva-repository-admin-default/src/main/java/org/apache/archiva/admin/repository/group/DefaultRepositoryGroupAdmin.java
+++ 
b/archiva-modules/archiva-base/archiva-repository-admin/archiva-repository-admin-default/src/main/java/org/apache/archiva/admin/repository/group/DefaultRepositoryGroupAdmin.java
@@ -29,11 +29,15 @@ import 
org.apache.archiva.admin.model.managed.ManagedRepositoryAdmin;
 import org.apache.archiva.admin.repository.AbstractRepositoryAdmin;
 import org.apache.archiva.configuration.Configuration;
 import org.apache.archiva.configuration.RepositoryGroupConfiguration;
+import org.apache.archiva.event.Event;
+import org.apache.archiva.event.EventHandler;
+import org.apache.archiva.event.EventType;
 import org.apache.archiva.metadata.model.facets.AuditEvent;
 import org.apache.archiva.indexer.merger.MergedRemoteIndexesScheduler;
 import org.apache.archiva.repository.EditableRepositoryGroup;
 import org.apache.archiva.repository.RepositoryException;
 import org.apache.archiva.repository.RepositoryRegistry;
+import org.apache.archiva.repository.event.RepositoryRegistryEvent;
 import org.apache.archiva.repository.features.IndexCreationFeature;
 import org.apache.archiva.repository.storage.StorageAsset;
 import org.apache.commons.lang3.StringUtils;
@@ -62,7 +66,7 @@ import java.util.stream.Collectors;
 @Service("repositoryGroupAdmin#default")
 public class DefaultRepositoryGroupAdmin
     extends AbstractRepositoryAdmin
-    implements RepositoryGroupAdmin
+    implements RepositoryGroupAdmin, EventHandler<Event>
 {
 
     private Logger log = LoggerFactory.getLogger( getClass() );
@@ -82,6 +86,10 @@ public class DefaultRepositoryGroupAdmin
     private Path groupsDirectory;
 
     @PostConstruct
+    public void baseInit() {
+        this.repositoryRegistry.registerEventHandler( EventType.ROOT, this );
+    }
+
     public void initialize()
     {
         String appServerBase = getRegistry().getString( "appserver.base" );
@@ -438,4 +446,14 @@ public class DefaultRepositoryGroupAdmin
         rg.setLocation( group.getLocation().toString() );
         return rg;
     }
+
+    @Override
+    public void handle( Event event )
+    {
+        if ( EventType.isInstanceOf( event.getType( ), 
RepositoryRegistryEvent.INITIALIZED ) )
+        {
+            log.debug( "Initializing RepositoryGroupAdmin" );
+            initialize();
+        }
+    }
 }
diff --git 
a/archiva-modules/archiva-base/archiva-repository-admin/archiva-repository-admin-default/src/test/java/org/apache/archiva/admin/repository/AbstractRepositoryAdminTest.java
 
b/archiva-modules/archiva-base/archiva-repository-admin/archiva-repository-admin-default/src/test/java/org/apache/archiva/admin/repository/AbstractRepositoryAdminTest.java
index d3086e2..993151a 100644
--- 
a/archiva-modules/archiva-base/archiva-repository-admin/archiva-repository-admin-default/src/test/java/org/apache/archiva/admin/repository/AbstractRepositoryAdminTest.java
+++ 
b/archiva-modules/archiva-base/archiva-repository-admin/archiva-repository-admin-default/src/test/java/org/apache/archiva/admin/repository/AbstractRepositoryAdminTest.java
@@ -31,6 +31,7 @@ import org.apache.archiva.configuration.ArchivaConfiguration;
 import org.apache.archiva.redback.role.RoleManager;
 import org.apache.archiva.redback.users.User;
 import org.apache.archiva.redback.users.memory.SimpleUser;
+import org.apache.archiva.repository.base.RepositoryGroupHandler;
 import org.apache.archiva.test.utils.ArchivaSpringJUnit4ClassRunner;
 import org.apache.commons.lang3.StringUtils;
 import org.junit.Before;
@@ -84,6 +85,10 @@ public abstract class AbstractRepositoryAdminTest
     @Inject
     private ArchivaConfiguration archivaConfiguration;
 
+    @SuppressWarnings( "unused" )
+    @Inject
+    RepositoryGroupHandler repositoryGroupHandler;
+
     @Before
     public void initialize() {
         Path confFile = Paths.get(APPSERVER_BASE_PATH, "conf/archiva.xml");
diff --git 
a/archiva-modules/archiva-base/archiva-repository-admin/archiva-repository-admin-default/src/test/java/org/apache/archiva/admin/repository/group/RepositoryGroupAdminTest.java
 
b/archiva-modules/archiva-base/archiva-repository-admin/archiva-repository-admin-default/src/test/java/org/apache/archiva/admin/repository/group/RepositoryGroupAdminTest.java
index ed6e286..49fd385 100644
--- 
a/archiva-modules/archiva-base/archiva-repository-admin/archiva-repository-admin-default/src/test/java/org/apache/archiva/admin/repository/group/RepositoryGroupAdminTest.java
+++ 
b/archiva-modules/archiva-base/archiva-repository-admin/archiva-repository-admin-default/src/test/java/org/apache/archiva/admin/repository/group/RepositoryGroupAdminTest.java
@@ -26,6 +26,7 @@ import 
org.apache.archiva.admin.repository.AbstractRepositoryAdminTest;
 import org.apache.archiva.metadata.model.facets.AuditEvent;
 import org.apache.archiva.repository.Repository;
 import org.apache.archiva.repository.RepositoryRegistry;
+import org.apache.archiva.repository.base.RepositoryGroupHandler;
 import org.junit.Test;
 
 import javax.inject.Inject;
@@ -44,6 +45,9 @@ public class RepositoryGroupAdminTest
     @Inject
     RepositoryRegistry repositoryRegistry;
 
+    @Inject
+    RepositoryGroupHandler repositoryGroupHandler;
+
     @Test
     public void addAndDeleteGroup()
         throws Exception
diff --git 
a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/event/RepositoryRegistryEvent.java
 
b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/event/RepositoryRegistryEvent.java
index 1e11579..7f3b62d 100644
--- 
a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/event/RepositoryRegistryEvent.java
+++ 
b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/event/RepositoryRegistryEvent.java
@@ -47,6 +47,21 @@ public class RepositoryRegistryEvent extends Event
      */
     public static EventType<RepositoryRegistryEvent> INITIALIZED = new 
EventType(ANY, "REGISTRY.INITIALIZED");
 
+    /**
+     * When the repository groups are initialized
+     */
+    public static EventType<RepositoryRegistryEvent> GROUPS_INITIALIZED = new 
EventType(ANY, "REGISTRY.GROUPS_INITIALIZED");
+
+    /**
+     * When the managed repositories are initialized
+     */
+    public static EventType<RepositoryRegistryEvent> MANAGED_REPOS_INITIALIZED 
= new EventType(ANY, "REGISTRY.MANAGED_REPOS_INITIALIZED");
+
+    /**
+     * When the remote repositories are initialized
+     */
+    public static EventType<RepositoryRegistryEvent> REMOTE_REPOS_INITIALIZED 
= new EventType(ANY, "REGISTRY.REMOTE_REPOS_INITIALIZED");
+
     public RepositoryRegistryEvent(EventType<? extends 
RepositoryRegistryEvent> type, Object origin) {
         super(type, origin);
     }
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 b909e24..6a3d0e0 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
@@ -28,7 +28,6 @@ import 
org.apache.archiva.components.registry.RegistryException;
 import org.apache.archiva.repository.EditableManagedRepository;
 import org.apache.archiva.repository.EditableRemoteRepository;
 import org.apache.archiva.repository.EditableRepository;
-import org.apache.archiva.repository.EditableRepositoryGroup;
 import org.apache.archiva.repository.ManagedRepository;
 import org.apache.archiva.repository.RemoteRepository;
 import org.apache.archiva.repository.Repository;
@@ -55,12 +54,11 @@ import javax.annotation.PreDestroy;
 import javax.inject.Inject;
 import javax.inject.Named;
 import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-import static 
org.apache.archiva.indexer.ArchivaIndexManager.DEFAULT_INDEX_PATH;
-
 /**
  * Registry for repositories. This is the central entry point for 
repositories. It provides methods for
  * retrieving, adding and removing repositories.
@@ -78,6 +76,7 @@ public class ArchivaRepositoryRegistry implements 
ConfigurationListener, EventHa
 {
 
     private static final Logger log = 
LoggerFactory.getLogger(RepositoryRegistry.class);
+    private final ConfigurationHandler configurationHandler;
 
     /**
      * We inject all repository providers
@@ -89,15 +88,14 @@ public class ArchivaRepositoryRegistry implements 
ConfigurationListener, EventHa
     IndexManagerFactory indexManagerFactory;
 
     @Inject
-    ArchivaConfiguration archivaConfiguration;
-
-    @Inject
     List<MetadataReader> metadataReaderList;
 
     @Inject
     @Named("repositoryContentFactory#default")
     RepositoryContentFactory repositoryContentFactory;
 
+
+
     private final EventManager eventManager;
 
 
@@ -107,20 +105,23 @@ public class ArchivaRepositoryRegistry implements 
ConfigurationListener, EventHa
     private Map<String, RemoteRepository> remoteRepositories = new HashMap<>();
     private Map<String, RemoteRepository> uRemoteRepositories = 
Collections.unmodifiableMap(remoteRepositories);
 
-    private Map<String, RepositoryGroup> repositoryGroups = new HashMap<>();
-    private Map<String, RepositoryGroup> uRepositoryGroups = 
Collections.unmodifiableMap(repositoryGroups);
-
     private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
 
-    private volatile boolean ignoreConfigEvents = false;
+    private RepositoryGroupHandler groupHandler;
 
-    public ArchivaRepositoryRegistry() {
+    private AtomicBoolean groups_initalized = new AtomicBoolean( false );
+    private AtomicBoolean managed_initialized = new AtomicBoolean( false );
+    private AtomicBoolean remote_initialized = new AtomicBoolean( false );
+
+
+    public ArchivaRepositoryRegistry(ConfigurationHandler 
configurationHandler) {
         this.eventManager = new EventManager(this);
+        this.configurationHandler = configurationHandler;
     }
 
     @Override
     public void setArchivaConfiguration( ArchivaConfiguration 
archivaConfiguration ) {
-        this.archivaConfiguration = archivaConfiguration;
+        this.configurationHandler.setArchivaConfiguration( 
archivaConfiguration );
     }
 
     @PostConstruct
@@ -128,21 +129,41 @@ public class ArchivaRepositoryRegistry implements 
ConfigurationListener, EventHa
         rwLock.writeLock().lock();
         try {
             log.debug("Initializing repository registry");
-            updateManagedRepositoriesFromConfig();
-            updateRemoteRepositoriesFromConfig();
-
-            repositoryGroups.clear();
-            Map<String, RepositoryGroup> repositoryGroups = 
getRepositorGroupsFromConfig();
-            this.repositoryGroups.putAll(repositoryGroups);
-
-            // archivaConfiguration.addChangeListener(this);
-            archivaConfiguration.addListener(this);
+            updateManagedRepositoriesFromConfig( );
+            pushEvent( new RepositoryRegistryEvent( 
RepositoryRegistryEvent.MANAGED_REPOS_INITIALIZED, this ) );
+            managed_initialized.set( true );
+            updateRemoteRepositoriesFromConfig( );
+            pushEvent( new RepositoryRegistryEvent( 
RepositoryRegistryEvent.REMOTE_REPOS_INITIALIZED, this ) );
+            remote_initialized.set( true );
+
+            initializeRepositoryGroups();
+            this.configurationHandler.addListener(this);
         } finally {
             rwLock.writeLock().unlock();
         }
         pushEvent(new 
RepositoryRegistryEvent(RepositoryRegistryEvent.RELOADED, this));
+        if (managed_initialized.get() && remote_initialized.get() && 
groups_initalized.get( )) {
+            pushEvent( new RepositoryRegistryEvent( 
RepositoryRegistryEvent.INITIALIZED, this ) );
+        }
     }
 
+    private void initializeRepositoryGroups() {
+        if (this.groupHandler!=null) {
+            this.groupHandler.initializeFromConfig();
+            this.groups_initalized.set( true );
+            pushEvent( new RepositoryRegistryEvent( 
RepositoryRegistryEvent.GROUPS_INITIALIZED, this ) );
+        }
+    }
+
+    public void registerGroupHandler(RepositoryGroupHandler groupHandler) {
+        this.groupHandler = groupHandler;
+        initializeRepositoryGroups();
+        if (managed_initialized.get() && remote_initialized.get() && 
groups_initalized.get( )) {
+            pushEvent( new RepositoryRegistryEvent( 
RepositoryRegistryEvent.INITIALIZED, this ) );
+        }
+    }
+
+
     @PreDestroy
     public void destroy() {
         for (ManagedRepository rep : managedRepositories.values()) {
@@ -153,11 +174,12 @@ public class ArchivaRepositoryRegistry implements 
ConfigurationListener, EventHa
             repo.close();
         }
         remoteRepositories.clear();
+        groupHandler.close();
         pushEvent(new 
RepositoryRegistryEvent(RepositoryRegistryEvent.DESTROYED, this));
     }
 
 
-    private Map<RepositoryType, RepositoryProvider> createProviderMap() {
+    protected Map<RepositoryType, RepositoryProvider> 
getRepositoryProviderMap() {
         Map<RepositoryType, RepositoryProvider> map = new HashMap<>();
         if (repositoryProviders != null) {
             for (RepositoryProvider provider : repositoryProviders) {
@@ -169,7 +191,7 @@ public class ArchivaRepositoryRegistry implements 
ConfigurationListener, EventHa
         return map;
     }
 
-    private RepositoryProvider getProvider(RepositoryType type) throws 
RepositoryException
+    protected RepositoryProvider getProvider(RepositoryType type) throws 
RepositoryException
     {
         return repositoryProviders.stream().filter(repositoryProvider -> 
repositoryProvider.provides().contains(type)).findFirst().orElseThrow(() -> new 
RepositoryException("Repository type cannot be handled: " + type));
     }
@@ -182,7 +204,7 @@ public class ArchivaRepositoryRegistry implements 
ConfigurationListener, EventHa
 
             Set<String> configRepoIds = new HashSet<>();
             List<ManagedRepositoryConfiguration> managedRepoConfigs =
-                    
getArchivaConfiguration().getConfiguration().getManagedRepositories();
+                    
configurationHandler.getBaseConfiguration().getManagedRepositories();
 
             if (managedRepoConfigs == null) {
                 return;
@@ -299,7 +321,7 @@ public class ArchivaRepositoryRegistry implements 
ConfigurationListener, EventHa
     private void updateRemoteRepositoriesFromConfig() {
         try {
             List<RemoteRepositoryConfiguration> remoteRepoConfigs =
-                    
getArchivaConfiguration().getConfiguration().getRemoteRepositories();
+                    
configurationHandler.getBaseConfiguration().getRemoteRepositories();
 
             if (remoteRepoConfigs == null) {
                 return;
@@ -341,53 +363,8 @@ public class ArchivaRepositoryRegistry implements 
ConfigurationListener, EventHa
         repo.registerEventHandler(RepositoryEvent.ANY, this);
     }
 
-    private Map<String, RepositoryGroup> getRepositorGroupsFromConfig() {
-        try {
-            List<RepositoryGroupConfiguration> repositoryGroupConfigurations =
-                    
getArchivaConfiguration().getConfiguration().getRepositoryGroups();
-
-            if (repositoryGroupConfigurations == null) {
-                return Collections.emptyMap();
-            }
-
-            Map<String, RepositoryGroup> repositoryGroupMap = new 
LinkedHashMap<>(repositoryGroupConfigurations.size());
-
-            Map<RepositoryType, RepositoryProvider> providerMap = 
createProviderMap();
-            for (RepositoryGroupConfiguration repoConfig : 
repositoryGroupConfigurations) {
-                RepositoryType repositoryType = 
RepositoryType.valueOf(repoConfig.getType());
-                if (providerMap.containsKey(repositoryType)) {
-                    try {
-                        RepositoryGroup repo = 
createNewRepositoryGroup(providerMap.get(repositoryType), repoConfig);
-                        repositoryGroupMap.put(repo.getId(), repo);
-                    } catch (Exception e) {
-                        log.error("Could not create repository group {}: {}", 
repoConfig.getId(), e.getMessage(), e);
-                    }
-                }
-            }
-            return repositoryGroupMap;
-        } catch (Throwable e) {
-            log.error("Could not initialize repositories from config: {}", 
e.getMessage(), e);
-            return Collections.emptyMap();
-        }
-    }
-
-    private RepositoryGroup createNewRepositoryGroup(RepositoryProvider 
provider, RepositoryGroupConfiguration config) throws RepositoryException {
-        RepositoryGroup repositoryGroup = 
provider.createRepositoryGroup(config);
-        repositoryGroup.registerEventHandler(RepositoryEvent.ANY, this);
-        updateRepositoryReferences(provider, repositoryGroup, config);
-        return repositoryGroup;
-    }
 
-    private void updateRepositoryReferences(RepositoryProvider provider, 
RepositoryGroup group, RepositoryGroupConfiguration configuration) {
-        if (group instanceof EditableRepositoryGroup ) {
-            EditableRepositoryGroup eGroup = (EditableRepositoryGroup) group;
-            
eGroup.setRepositories(configuration.getRepositories().stream().map(r -> 
getManagedRepository(r)).collect(Collectors.toList()));
-        }
-    }
 
-    private ArchivaConfiguration getArchivaConfiguration() {
-        return this.archivaConfiguration;
-    }
 
     /**
      * Returns all repositories that are registered. There is no defined order 
of the returned repositories.
@@ -438,7 +415,7 @@ public class ArchivaRepositoryRegistry implements 
ConfigurationListener, EventHa
     public Collection<RepositoryGroup> getRepositoryGroups( ) {
         rwLock.readLock().lock();
         try {
-            return uRepositoryGroups.values();
+            return groupHandler.getRepositoryGroups( );
         } finally {
             rwLock.readLock().unlock();
         }
@@ -462,8 +439,8 @@ public class ArchivaRepositoryRegistry implements 
ConfigurationListener, EventHa
             } else if (remoteRepositories.containsKey(repoId)) {
                 log.debug("Remote repo");
                 return remoteRepositories.get(repoId);
-            } else if (repositoryGroups.containsKey(repoId)) {
-                return repositoryGroups.get(repoId);
+            } else if (groupHandler.hasRepositoryGroup(repoId)) {
+                return groupHandler.getRepositoryGroup(repoId);
             } else {
                 return null;
             }
@@ -510,23 +487,14 @@ public class ArchivaRepositoryRegistry implements 
ConfigurationListener, EventHa
     public RepositoryGroup getRepositoryGroup( String groupId ) {
         rwLock.readLock().lock();
         try {
-            return repositoryGroups.get(groupId);
+            return groupHandler.getRepositoryGroup(groupId);
         } finally {
             rwLock.readLock().unlock();
         }
     }
 
-    /*
-     * The <code>ignoreConfigEvents</code> works only for synchronized 
configuration events.
-     * If the configuration throws async events, we cannot know, if the event 
is caused by this instance or another thread.
-     */
-    private void saveConfiguration(Configuration configuration) throws 
IndeterminateConfigurationException, RegistryException {
-        ignoreConfigEvents = true;
-        try {
-            getArchivaConfiguration().save(configuration);
-        } finally {
-            ignoreConfigEvents = false;
-        }
+    protected void saveConfiguration(Configuration configuration) throws 
IndeterminateConfigurationException, RegistryException {
+        configurationHandler.save(configuration, 
ConfigurationHandler.REGISTRY_EVENT_TAG );
     }
 
     /**
@@ -552,7 +520,7 @@ public class ArchivaRepositoryRegistry implements 
ConfigurationListener, EventHa
                 }
                 RepositoryProvider provider = 
getProvider(managedRepository.getType());
                 ManagedRepositoryConfiguration newCfg = 
provider.getManagedConfiguration(managedRepository);
-                Configuration configuration = 
getArchivaConfiguration().getConfiguration();
+                Configuration configuration = 
configurationHandler.getBaseConfiguration();
                 updateRepositoryReferences(provider, managedRepository, 
newCfg, configuration);
                 ManagedRepositoryConfiguration oldCfg = 
configuration.findManagedRepositoryById(id);
                 if (oldCfg != null) {
@@ -595,7 +563,7 @@ public class ArchivaRepositoryRegistry implements 
ConfigurationListener, EventHa
         try {
             final String id = managedRepositoryConfiguration.getId();
             final RepositoryType repositoryType = 
RepositoryType.valueOf(managedRepositoryConfiguration.getType());
-            Configuration configuration = 
getArchivaConfiguration().getConfiguration();
+            Configuration configuration = 
configurationHandler.getBaseConfiguration();
             ManagedRepository repo = managedRepositories.get(id);
             ManagedRepositoryConfiguration oldCfg = repo != null ? 
getProvider(repositoryType).getManagedConfiguration(repo) : null;
             repo = putRepository(managedRepositoryConfiguration, 
configuration);
@@ -670,33 +638,10 @@ public class ArchivaRepositoryRegistry implements 
ConfigurationListener, EventHa
     public RepositoryGroup putRepositoryGroup( RepositoryGroup repositoryGroup 
) throws RepositoryException {
         rwLock.writeLock().lock();
         try {
-            final String id = repositoryGroup.getId();
-            RepositoryGroup originRepoGroup = repositoryGroups.put(id, 
repositoryGroup);
-            try {
-                if (originRepoGroup != null && originRepoGroup != 
repositoryGroup) {
-                    originRepoGroup.close();
-                }
-                RepositoryProvider provider = 
getProvider(repositoryGroup.getType());
-                RepositoryGroupConfiguration newCfg = 
provider.getRepositoryGroupConfiguration(repositoryGroup);
-                Configuration configuration = 
getArchivaConfiguration().getConfiguration();
-                updateRepositoryReferences(provider, repositoryGroup, newCfg);
-                RepositoryGroupConfiguration oldCfg = 
configuration.findRepositoryGroupById(id);
-                if (oldCfg != null) {
-                    configuration.removeRepositoryGroup(oldCfg);
-                }
-                configuration.addRepositoryGroup(newCfg);
-                saveConfiguration(configuration);
-                return repositoryGroup;
-            } catch (Exception e) {
-                // Rollback
-                if (originRepoGroup != null) {
-                    repositoryGroups.put(id, originRepoGroup);
-                } else {
-                    repositoryGroups.remove(id);
-                }
-                log.error("Exception during configuration update {}", 
e.getMessage(), e);
-                throw new RepositoryException("Could not save the 
configuration" + (e.getMessage() == null ? "" : ": " + e.getMessage()));
+            if (this.groupHandler==null) {
+                throw new RepositoryException( "Fatal error. 
RepositoryGroupHandler not registered!" );
             }
+            return this.groupHandler.putRepositoryGroup( repositoryGroup );
         } finally {
             rwLock.writeLock().unlock();
         }
@@ -714,22 +659,7 @@ public class ArchivaRepositoryRegistry implements 
ConfigurationListener, EventHa
     public RepositoryGroup putRepositoryGroup( RepositoryGroupConfiguration 
repositoryGroupConfiguration ) throws RepositoryException {
         rwLock.writeLock().lock();
         try {
-            final String id = repositoryGroupConfiguration.getId();
-            final RepositoryType repositoryType = 
RepositoryType.valueOf(repositoryGroupConfiguration.getType());
-            Configuration configuration = 
getArchivaConfiguration().getConfiguration();
-            RepositoryGroup repo = repositoryGroups.get(id);
-            RepositoryGroupConfiguration oldCfg = repo != null ? 
getProvider(repositoryType).getRepositoryGroupConfiguration(repo) : null;
-            repo = putRepositoryGroup(repositoryGroupConfiguration, 
configuration);
-            try {
-                saveConfiguration(configuration);
-            } catch (IndeterminateConfigurationException | RegistryException 
e) {
-                if (oldCfg != null) {
-                    
getProvider(repositoryType).updateRepositoryGroupInstance((EditableRepositoryGroup)
 repo, oldCfg);
-                }
-                log.error("Could not save the configuration for repository 
group {}: {}", id, e.getMessage(), e);
-                throw new RepositoryException("Could not save the 
configuration for repository group " + id + ": " + e.getMessage());
-            }
-            return repo;
+            return groupHandler.putRepositoryGroup( 
repositoryGroupConfiguration );
         } finally {
             rwLock.writeLock().unlock();
         }
@@ -749,41 +679,12 @@ public class ArchivaRepositoryRegistry implements 
ConfigurationListener, EventHa
     public RepositoryGroup putRepositoryGroup( RepositoryGroupConfiguration 
repositoryGroupConfiguration, Configuration configuration ) throws 
RepositoryException {
         rwLock.writeLock().lock();
         try {
-            final String id = repositoryGroupConfiguration.getId();
-            final RepositoryType repoType = 
RepositoryType.valueOf(repositoryGroupConfiguration.getType());
-            RepositoryGroup repo;
-            setRepositoryGroupDefaults(repositoryGroupConfiguration);
-            if (repositoryGroups.containsKey(id)) {
-                repo = repositoryGroups.get(id);
-                if (repo instanceof EditableRepositoryGroup) {
-                    
getProvider(repoType).updateRepositoryGroupInstance((EditableRepositoryGroup) 
repo, repositoryGroupConfiguration);
-                } else {
-                    throw new RepositoryException("The repository is not 
editable " + id);
-                }
-            } else {
-                repo = 
getProvider(repoType).createRepositoryGroup(repositoryGroupConfiguration);
-                repositoryGroups.put(id, repo);
-            }
-            updateRepositoryReferences(getProvider(repoType), repo, 
repositoryGroupConfiguration);
-            replaceOrAddRepositoryConfig(repositoryGroupConfiguration, 
configuration);
-            return repo;
+            return groupHandler.putRepositoryGroup( 
repositoryGroupConfiguration, configuration );
         } finally {
             rwLock.writeLock().unlock();
         }
     }
 
-    private void setRepositoryGroupDefaults(RepositoryGroupConfiguration 
repositoryGroupConfiguration) {
-        if 
(StringUtils.isEmpty(repositoryGroupConfiguration.getMergedIndexPath())) {
-            
repositoryGroupConfiguration.setMergedIndexPath(DEFAULT_INDEX_PATH);
-        }
-        if (repositoryGroupConfiguration.getMergedIndexTtl() <= 0) {
-            repositoryGroupConfiguration.setMergedIndexTtl(300);
-        }
-        if 
(StringUtils.isEmpty(repositoryGroupConfiguration.getCronExpression())) {
-            repositoryGroupConfiguration.setCronExpression("0 0 03 ? * MON");
-        }
-    }
-
     private void replaceOrAddRepositoryConfig(ManagedRepositoryConfiguration 
managedRepositoryConfiguration, Configuration configuration) {
         if (configuration != null) {
             ManagedRepositoryConfiguration oldCfg = 
configuration.findManagedRepositoryById(managedRepositoryConfiguration.getId());
@@ -874,7 +775,7 @@ public class ArchivaRepositoryRegistry implements 
ConfigurationListener, EventHa
     public RemoteRepository putRepository( RemoteRepository remoteRepository ) 
throws RepositoryException {
         rwLock.writeLock().lock();
         try {
-            Configuration configuration = 
getArchivaConfiguration().getConfiguration();
+            Configuration configuration = 
configurationHandler.getBaseConfiguration();
             try {
                 RemoteRepository repo = putRepository(remoteRepository, 
configuration);
                 saveConfiguration(configuration);
@@ -902,7 +803,7 @@ public class ArchivaRepositoryRegistry implements 
ConfigurationListener, EventHa
         try {
             final String id = remoteRepositoryConfiguration.getId();
             final RepositoryType repositoryType = 
RepositoryType.valueOf(remoteRepositoryConfiguration.getType());
-            Configuration configuration = 
getArchivaConfiguration().getConfiguration();
+            Configuration configuration = 
configurationHandler.getBaseConfiguration();
             RemoteRepository repo = remoteRepositories.get(id);
             RemoteRepositoryConfiguration oldCfg = repo != null ? 
getProvider(repositoryType).getRemoteConfiguration(repo) : null;
             repo = putRepository(remoteRepositoryConfiguration, configuration);
@@ -1012,8 +913,8 @@ public class ArchivaRepositoryRegistry implements 
ConfigurationListener, EventHa
                 repo = managedRepositories.remove(id);
                 if (repo != null) {
                     repo.close();
-                    removeRepositoryFromGroups(repo);
-                    Configuration configuration = 
getArchivaConfiguration().getConfiguration();
+                    this.groupHandler.removeRepositoryFromGroups(repo);
+                    Configuration configuration = 
configurationHandler.getBaseConfiguration();
                     ManagedRepositoryConfiguration cfg = 
configuration.findManagedRepositoryById(id);
                     if (cfg != null) {
                         configuration.removeManagedRepository(cfg);
@@ -1032,12 +933,6 @@ public class ArchivaRepositoryRegistry implements 
ConfigurationListener, EventHa
         }
     }
 
-    private void removeRepositoryFromGroups(ManagedRepository repo) {
-        if (repo != null) {
-            repositoryGroups.values().stream().filter(repoGroup -> repoGroup 
instanceof EditableRepository).
-                    map(repoGroup -> (EditableRepositoryGroup) 
repoGroup).forEach(repoGroup -> repoGroup.removeRepository(repo));
-        }
-    }
 
     @Override
     public void removeRepository( ManagedRepository managedRepository, 
Configuration configuration ) throws RepositoryException {
@@ -1052,7 +947,7 @@ public class ArchivaRepositoryRegistry implements 
ConfigurationListener, EventHa
                 repo = managedRepositories.remove(id);
                 if (repo != null) {
                     repo.close();
-                    removeRepositoryFromGroups(repo);
+                    this.groupHandler.removeRepositoryFromGroups(repo);
                     ManagedRepositoryConfiguration cfg = 
configuration.findManagedRepositoryById(id);
                     if (cfg != null) {
                         configuration.removeManagedRepository(cfg);
@@ -1080,26 +975,10 @@ public class ArchivaRepositoryRegistry implements 
ConfigurationListener, EventHa
             return;
         }
         final String id = repositoryGroup.getId();
-        RepositoryGroup repo = getRepositoryGroup(id);
-        if (repo != null) {
+        if (groupHandler.hasRepositoryGroup( id )) {
             rwLock.writeLock().lock();
             try {
-                repo = repositoryGroups.remove(id);
-                if (repo != null) {
-                    repo.close();
-                    Configuration configuration = 
getArchivaConfiguration().getConfiguration();
-                    RepositoryGroupConfiguration cfg = 
configuration.findRepositoryGroupById(id);
-                    if (cfg != null) {
-                        configuration.removeRepositoryGroup(cfg);
-                    }
-                    saveConfiguration(configuration);
-                }
-
-            } catch (RegistryException | IndeterminateConfigurationException 
e) {
-                // Rollback
-                log.error("Could not save config after repository removal: 
{}", e.getMessage(), e);
-                repositoryGroups.put(repo.getId(), repo);
-                throw new RepositoryException("Could not save configuration 
after repository removal: " + e.getMessage());
+            groupHandler.removeRepositoryGroup( id );
             } finally {
                 rwLock.writeLock().unlock();
             }
@@ -1112,23 +991,14 @@ public class ArchivaRepositoryRegistry implements 
ConfigurationListener, EventHa
             return;
         }
         final String id = repositoryGroup.getId();
-        RepositoryGroup repo = getRepositoryGroup(id);
-        if (repo != null) {
+        if (groupHandler.hasRepositoryGroup( id )) {
             rwLock.writeLock().lock();
             try {
-                repo = repositoryGroups.remove(id);
-                if (repo != null) {
-                    repo.close();
-                    RepositoryGroupConfiguration cfg = 
configuration.findRepositoryGroupById(id);
-                    if (cfg != null) {
-                        configuration.removeRepositoryGroup(cfg);
-                    }
-                }
+                groupHandler.removeRepositoryGroup( id, configuration );
             } finally {
                 rwLock.writeLock().unlock();
             }
         }
-
     }
 
     private void doRemoveRepo(RemoteRepository repo, Configuration 
configuration) {
@@ -1164,7 +1034,7 @@ public class ArchivaRepositoryRegistry implements 
ConfigurationListener, EventHa
             try {
                 repo = remoteRepositories.remove(id);
                 if (repo != null) {
-                    Configuration configuration = 
getArchivaConfiguration().getConfiguration();
+                    Configuration configuration = 
configurationHandler.getBaseConfiguration();
                     doRemoveRepo(repo, configuration);
                     saveConfiguration(configuration);
                 }
@@ -1292,8 +1162,9 @@ public class ArchivaRepositoryRegistry implements 
ConfigurationListener, EventHa
 
     @Override
     public void configurationEvent(ConfigurationEvent event) {
-        // Note: the ignoreConfigEvents flag does not work, if the config 
events are asynchronous.
-        if (!ignoreConfigEvents) {
+        // We ignore the event, if the save was triggered by ourself
+        if ( !ConfigurationHandler.REGISTRY_EVENT_TAG.equals( event.getTag( ) 
) )
+        {
             reload();
         }
     }
diff --git 
a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/ConfigurationHandler.java
 
b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/ConfigurationHandler.java
new file mode 100644
index 0000000..c4ee513
--- /dev/null
+++ 
b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/ConfigurationHandler.java
@@ -0,0 +1,71 @@
+package org.apache.archiva.repository.base;
+/*
+ * 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.components.registry.RegistryException;
+import org.apache.archiva.configuration.ArchivaConfiguration;
+import org.apache.archiva.configuration.Configuration;
+import org.apache.archiva.configuration.ConfigurationListener;
+import org.apache.archiva.configuration.IndeterminateConfigurationException;
+import org.springframework.stereotype.Service;
+
+/**
+ * This is just a simple wrapper to access the archiva configuration used by 
the registry and associated classes
+ *
+ * @author Martin Stockhammer <[email protected]>
+ */
+@Service("configurationHandler#default")
+public class ConfigurationHandler
+{
+    public static final String REGISTRY_EVENT_TAG = "repositoryRegistry";
+
+    private ArchivaConfiguration archivaConfiguration;
+
+    public ConfigurationHandler( ArchivaConfiguration archivaConfiguration ) {
+        this.archivaConfiguration = archivaConfiguration;
+    }
+
+    public void addListener( ConfigurationListener listener ) {
+        this.archivaConfiguration.addListener( listener );
+    }
+
+    public ArchivaConfiguration getArchivaConfiguration( )
+    {
+        return archivaConfiguration;
+    }
+
+    public void setArchivaConfiguration( ArchivaConfiguration 
archivaConfiguration )
+    {
+        this.archivaConfiguration = archivaConfiguration;
+    }
+
+    public Configuration getBaseConfiguration() {
+        return archivaConfiguration.getConfiguration( );
+    }
+
+    public void save(Configuration configuration, String eventTag) throws 
IndeterminateConfigurationException, RegistryException
+    {
+        archivaConfiguration.save( configuration, eventTag);
+    }
+
+    public void save(Configuration configuration) throws 
IndeterminateConfigurationException, RegistryException
+    {
+        archivaConfiguration.save( configuration, "" );
+    }
+
+}
diff --git 
a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/RepositoryGroupHandler.java
 
b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/RepositoryGroupHandler.java
new file mode 100644
index 0000000..f4b02a2
--- /dev/null
+++ 
b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/RepositoryGroupHandler.java
@@ -0,0 +1,328 @@
+package org.apache.archiva.repository.base;
+/*
+ * 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.components.registry.RegistryException;
+import org.apache.archiva.configuration.Configuration;
+import org.apache.archiva.configuration.IndeterminateConfigurationException;
+import org.apache.archiva.configuration.RepositoryGroupConfiguration;
+import org.apache.archiva.indexer.merger.MergedRemoteIndexesScheduler;
+import org.apache.archiva.repository.EditableRepository;
+import org.apache.archiva.repository.EditableRepositoryGroup;
+import org.apache.archiva.repository.ManagedRepository;
+import org.apache.archiva.repository.RepositoryException;
+import org.apache.archiva.repository.RepositoryGroup;
+import org.apache.archiva.repository.RepositoryProvider;
+import org.apache.archiva.repository.RepositoryType;
+import org.apache.archiva.repository.event.RepositoryEvent;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import javax.inject.Named;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static 
org.apache.archiva.indexer.ArchivaIndexManager.DEFAULT_INDEX_PATH;
+
+/**
+ * This class manages repository groups for the RepositoryRegistry.
+ * It is tightly coupled with the {@link ArchivaRepositoryRegistry}.
+ *
+ * @author Martin Stockhammer <[email protected]>
+ */
+@Service("repositoryGroupHandler#default")
+public class RepositoryGroupHandler
+{
+    private static final Logger log = 
LoggerFactory.getLogger(RepositoryGroupHandler.class);
+
+    private final ArchivaRepositoryRegistry repositoryRegistry;
+    private final ConfigurationHandler configurationHandler;
+    private final MergedRemoteIndexesScheduler mergedRemoteIndexesScheduler;
+
+    private Map<String, RepositoryGroup> repositoryGroups = new HashMap<>();
+
+    /**
+     * Creates a new instance. All dependencies are injected on the 
constructor.
+     * @param repositoryRegistry the registry. To avoid circular dependencies 
via DI, this class registers itself on the registry.
+     * @param configurationHandler the configuration handler is used to 
retrieve and save configuration.
+     * @param mergedRemoteIndexesScheduler the index scheduler is used for 
merging the indexes from all group members
+     */
+    public RepositoryGroupHandler( ArchivaRepositoryRegistry 
repositoryRegistry,
+                                   ConfigurationHandler configurationHandler,
+                                   
@Named("mergedRemoteIndexesScheduler#default") MergedRemoteIndexesScheduler 
mergedRemoteIndexesScheduler) {
+        this.configurationHandler = configurationHandler;
+        this.mergedRemoteIndexesScheduler = mergedRemoteIndexesScheduler;
+        this.repositoryRegistry = repositoryRegistry;
+    }
+
+    @PostConstruct
+    private void init() {
+        log.debug( "Initializing repository group handler " + 
repositoryRegistry.toString( ) );
+        // We are registering this class on the registry. This is necessary to 
avoid circular dependencies via injection.
+        this.repositoryRegistry.registerGroupHandler( this );
+    }
+
+    public void initializeFromConfig() {
+        this.repositoryGroups.clear();
+        this.repositoryGroups.putAll( getRepositorGroupsFromConfig( ) );
+    }
+
+    public Map<String, RepositoryGroup> getRepositorGroupsFromConfig() {
+        try {
+            List<RepositoryGroupConfiguration> repositoryGroupConfigurations =
+                
this.configurationHandler.getBaseConfiguration().getRepositoryGroups();
+
+            if (repositoryGroupConfigurations == null) {
+                return Collections.emptyMap();
+            }
+
+            Map<String, RepositoryGroup> repositoryGroupMap = new 
LinkedHashMap<>(repositoryGroupConfigurations.size());
+
+            Map<RepositoryType, RepositoryProvider> providerMap = 
repositoryRegistry.getRepositoryProviderMap();
+            for (RepositoryGroupConfiguration repoConfig : 
repositoryGroupConfigurations) {
+                RepositoryType repositoryType = 
RepositoryType.valueOf(repoConfig.getType());
+                if (providerMap.containsKey(repositoryType)) {
+                    try {
+                        RepositoryGroup repo = 
createNewRepositoryGroup(providerMap.get(repositoryType), repoConfig);
+                        repositoryGroupMap.put(repo.getId(), repo);
+                    } catch (Exception e) {
+                        log.error("Could not create repository group {}: {}", 
repoConfig.getId(), e.getMessage(), e);
+                    }
+                }
+            }
+            return repositoryGroupMap;
+        } catch (Throwable e) {
+            log.error("Could not initialize repositories from config: {}", 
e.getMessage(), e);
+            return Collections.emptyMap();
+        }
+    }
+
+    public RepositoryGroup createNewRepositoryGroup(RepositoryProvider 
provider, RepositoryGroupConfiguration config) throws RepositoryException
+    {
+        RepositoryGroup repositoryGroup = 
provider.createRepositoryGroup(config);
+        repositoryGroup.registerEventHandler( RepositoryEvent.ANY, 
repositoryRegistry);
+        updateRepositoryReferences(provider, repositoryGroup, config);
+        return repositoryGroup;
+    }
+
+    public void updateRepositoryReferences( RepositoryProvider provider, 
RepositoryGroup group, RepositoryGroupConfiguration configuration) {
+        if (group instanceof EditableRepositoryGroup ) {
+            EditableRepositoryGroup eGroup = (EditableRepositoryGroup) group;
+            eGroup.setRepositories(configuration.getRepositories().stream()
+                .map(r -> repositoryRegistry.getManagedRepository(r)).collect( 
Collectors.toList()));
+        }
+    }
+
+    /**
+     * Adds a new repository group to the current list, or replaces the 
repository group definition with
+     * the same id, if it exists already.
+     * The change is saved to the configuration immediately.
+     *
+     * @param repositoryGroup the new repository group.
+     * @throws RepositoryException if the new repository group could not be 
saved to the configuration.
+     */
+    public RepositoryGroup putRepositoryGroup( RepositoryGroup repositoryGroup 
) throws RepositoryException {
+            final String id = repositoryGroup.getId();
+            RepositoryGroup originRepoGroup = repositoryGroups.put(id, 
repositoryGroup);
+            try {
+                if (originRepoGroup != null && originRepoGroup != 
repositoryGroup) {
+                    originRepoGroup.close();
+                }
+                RepositoryProvider provider = repositoryRegistry.getProvider( 
repositoryGroup.getType());
+                RepositoryGroupConfiguration newCfg = 
provider.getRepositoryGroupConfiguration(repositoryGroup);
+                Configuration configuration = 
this.configurationHandler.getBaseConfiguration();
+                updateRepositoryReferences(provider, repositoryGroup, newCfg);
+                RepositoryGroupConfiguration oldCfg = 
configuration.findRepositoryGroupById(id);
+                if (oldCfg != null) {
+                    configuration.removeRepositoryGroup(oldCfg);
+                }
+                configuration.addRepositoryGroup(newCfg);
+                repositoryRegistry.saveConfiguration(configuration);
+                return repositoryGroup;
+            } catch (Exception e) {
+                // Rollback
+                if (originRepoGroup != null) {
+                    repositoryGroups.put(id, originRepoGroup);
+                } else {
+                    repositoryGroups.remove(id);
+                }
+                log.error("Exception during configuration update {}", 
e.getMessage(), e);
+                throw new RepositoryException("Could not save the 
configuration" + (e.getMessage() == null ? "" : ": " + e.getMessage()));
+            }
+    }
+
+    /**
+     * Adds a new repository group or updates the repository with the same id, 
if it exists already.
+     * The configuration is saved immediately.
+     *
+     * @param repositoryGroupConfiguration the repository configuration
+     * @return the updated or created repository
+     * @throws RepositoryException if an error occurs, or the configuration is 
not valid.
+     */
+    public RepositoryGroup putRepositoryGroup( RepositoryGroupConfiguration 
repositoryGroupConfiguration ) throws RepositoryException {
+            final String id = repositoryGroupConfiguration.getId();
+            final RepositoryType repositoryType = 
RepositoryType.valueOf(repositoryGroupConfiguration.getType());
+            Configuration configuration = 
this.configurationHandler.getBaseConfiguration();
+            RepositoryGroup repo = repositoryGroups.get(id);
+            RepositoryGroupConfiguration oldCfg = repo != null ? 
repositoryRegistry.getProvider(repositoryType).getRepositoryGroupConfiguration(repo)
 : null;
+            repo = putRepositoryGroup(repositoryGroupConfiguration, 
configuration);
+            try {
+                repositoryRegistry.saveConfiguration(configuration);
+            } catch ( IndeterminateConfigurationException | RegistryException 
e) {
+                if (oldCfg != null) {
+                    
repositoryRegistry.getProvider(repositoryType).updateRepositoryGroupInstance((EditableRepositoryGroup)
 repo, oldCfg);
+                }
+                log.error("Could not save the configuration for repository 
group {}: {}", id, e.getMessage(), e);
+                throw new RepositoryException("Could not save the 
configuration for repository group " + id + ": " + e.getMessage());
+            }
+            return repo;
+    }
+
+    public RepositoryGroup putRepositoryGroup( RepositoryGroupConfiguration 
repositoryGroupConfiguration, Configuration configuration ) throws 
RepositoryException {
+            final String id = repositoryGroupConfiguration.getId();
+            final RepositoryType repoType = 
RepositoryType.valueOf(repositoryGroupConfiguration.getType());
+            RepositoryGroup repo;
+            setRepositoryGroupDefaults(repositoryGroupConfiguration);
+            if (repositoryGroups.containsKey(id)) {
+                repo = repositoryGroups.get(id);
+                if (repo instanceof EditableRepositoryGroup) {
+                    
repositoryRegistry.getProvider(repoType).updateRepositoryGroupInstance((EditableRepositoryGroup)
 repo, repositoryGroupConfiguration);
+                } else {
+                    throw new RepositoryException("The repository is not 
editable " + id);
+                }
+            } else {
+                repo = 
repositoryRegistry.getProvider(repoType).createRepositoryGroup(repositoryGroupConfiguration);
+                repositoryGroups.put(id, repo);
+            }
+            
updateRepositoryReferences(repositoryRegistry.getProvider(repoType), repo, 
repositoryGroupConfiguration);
+            replaceOrAddRepositoryConfig(repositoryGroupConfiguration, 
configuration);
+            return repo;
+    }
+
+    private void setRepositoryGroupDefaults(RepositoryGroupConfiguration 
repositoryGroupConfiguration) {
+        if ( 
StringUtils.isEmpty(repositoryGroupConfiguration.getMergedIndexPath())) {
+            
repositoryGroupConfiguration.setMergedIndexPath(DEFAULT_INDEX_PATH);
+        }
+        if (repositoryGroupConfiguration.getMergedIndexTtl() <= 0) {
+            repositoryGroupConfiguration.setMergedIndexTtl(300);
+        }
+        if 
(StringUtils.isEmpty(repositoryGroupConfiguration.getCronExpression())) {
+            repositoryGroupConfiguration.setCronExpression("0 0 03 ? * MON");
+        }
+    }
+
+    private void replaceOrAddRepositoryConfig(RepositoryGroupConfiguration 
repositoryGroupConfiguration, Configuration configuration) {
+        RepositoryGroupConfiguration oldCfg = 
configuration.findRepositoryGroupById(repositoryGroupConfiguration.getId());
+        if (oldCfg != null) {
+            configuration.removeRepositoryGroup(oldCfg);
+        }
+        configuration.addRepositoryGroup(repositoryGroupConfiguration);
+    }
+
+    public void removeRepositoryFromGroups( ManagedRepository repo) {
+        if (repo != null) {
+            repositoryGroups.values().stream().filter(repoGroup -> repoGroup 
instanceof EditableRepository ).
+                map(repoGroup -> (EditableRepositoryGroup) 
repoGroup).forEach(repoGroup -> repoGroup.removeRepository(repo));
+        }
+    }
+
+    /**
+     * Removes a repository group from the registry and configuration, if it 
exists.
+     * The change is saved to the configuration immediately.
+     *
+     * @param id the id of the repository group to remove
+     * @throws RepositoryException if a error occurs during configuration save
+     */
+    public void removeRepositoryGroup( final String id ) throws 
RepositoryException {
+        RepositoryGroup repo = getRepositoryGroup(id);
+        if (repo != null) {
+            try {
+                repo = repositoryGroups.remove(id);
+                if (repo != null) {
+                    repo.close();
+                    Configuration configuration = 
this.configurationHandler.getBaseConfiguration();
+                    RepositoryGroupConfiguration cfg = 
configuration.findRepositoryGroupById(id);
+                    if (cfg != null) {
+                        configuration.removeRepositoryGroup(cfg);
+                    }
+                    this.configurationHandler.save(configuration, 
ConfigurationHandler.REGISTRY_EVENT_TAG );
+                }
+
+            } catch (RegistryException | IndeterminateConfigurationException 
e) {
+                // Rollback
+                log.error("Could not save config after repository removal: 
{}", e.getMessage(), e);
+                repositoryGroups.put(repo.getId(), repo);
+                throw new RepositoryException("Could not save configuration 
after repository removal: " + e.getMessage());
+            }
+        }
+    }
+
+    public void removeRepositoryGroup( String id, Configuration configuration 
) throws RepositoryException {
+        RepositoryGroup repo = repositoryGroups.get(id);
+        if (repo != null) {
+                repo = repositoryGroups.remove(id);
+                if (repo != null) {
+                    repo.close();
+                    RepositoryGroupConfiguration cfg = 
configuration.findRepositoryGroupById(id);
+                    if (cfg != null) {
+                        configuration.removeRepositoryGroup(cfg);
+                    }
+                }
+        }
+
+    }
+
+    public RepositoryGroup getRepositoryGroup( String groupId ) {
+        return repositoryGroups.get(groupId);
+    }
+
+    public Collection<RepositoryGroup> getRepositoryGroups() {
+        return repositoryGroups.values( );
+    }
+
+    public boolean hasRepositoryGroup(String id) {
+        return repositoryGroups.containsKey( id );
+    }
+
+    @PreDestroy
+    private void destroy() {
+        this.close( );
+    }
+
+    public void close() {
+        for (RepositoryGroup group : repositoryGroups.values()) {
+            try
+            {
+                group.close( );
+            } catch (Throwable e) {
+                log.error( "Could not close repository group {}: {}", 
group.getId( ), e.getMessage( ) );
+            }
+        }
+        this.repositoryGroups.clear();
+    }
+
+}
diff --git 
a/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/base/ArchivaRepositoryRegistryTest.java
 
b/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/base/ArchivaRepositoryRegistryTest.java
index 58965ed..b7cbf1f 100644
--- 
a/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/base/ArchivaRepositoryRegistryTest.java
+++ 
b/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/base/ArchivaRepositoryRegistryTest.java
@@ -66,6 +66,10 @@ public class ArchivaRepositoryRegistryTest
     @Inject
     ArchivaConfiguration archivaConfiguration;
 
+    @SuppressWarnings( "unused" )
+    @Inject
+    RepositoryGroupHandler repositoryGroupHandler;
+
     private static final Path userCfg = Paths.get(System.getProperty( 
"user.home" ), ".m2/archiva.xml");
 
     private static Path cfgCopy;
diff --git 
a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/MavenIndexManagerTest.java
 
b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/MavenIndexManagerTest.java
index c025180..e8fc6ce 100644
--- 
a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/MavenIndexManagerTest.java
+++ 
b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/MavenIndexManagerTest.java
@@ -24,6 +24,7 @@ import org.apache.archiva.indexer.ArchivaIndexingContext;
 import org.apache.archiva.indexer.IndexCreationFailedException;
 import org.apache.archiva.repository.base.ArchivaRepositoryRegistry;
 import org.apache.archiva.repository.RepositoryType;
+import org.apache.archiva.repository.base.RepositoryGroupHandler;
 import org.apache.archiva.repository.features.IndexCreationFeature;
 import org.apache.archiva.repository.features.RemoteIndexFeature;
 import org.apache.archiva.repository.maven.MavenManagedRepository;
@@ -61,6 +62,9 @@ public class MavenIndexManagerTest {
     @Inject
     ArchivaRepositoryRegistry repositoryRegistry;
 
+    @Inject
+    RepositoryGroupHandler groupHandler;
+
 
     private Path indexPath;
     private MavenManagedRepository repository;
diff --git 
a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/search/AbstractMavenRepositorySearch.java
 
b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/search/AbstractMavenRepositorySearch.java
index b94e6b8..575d078 100644
--- 
a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/search/AbstractMavenRepositorySearch.java
+++ 
b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/search/AbstractMavenRepositorySearch.java
@@ -31,6 +31,8 @@ import org.apache.archiva.indexer.search.SearchResults;
 import org.apache.archiva.proxy.ProxyRegistry;
 import org.apache.archiva.repository.base.ArchivaRepositoryRegistry;
 import org.apache.archiva.repository.Repository;
+import org.apache.archiva.repository.base.ConfigurationHandler;
+import org.apache.archiva.repository.base.RepositoryGroupHandler;
 import org.apache.archiva.repository.features.IndexCreationFeature;
 import org.apache.archiva.test.utils.ArchivaSpringJUnit4ClassRunner;
 import org.apache.commons.lang3.SystemUtils;
@@ -93,6 +95,9 @@ public abstract class AbstractMavenRepositorySearch
     ArchivaRepositoryRegistry repositoryRegistry;
 
     @Inject
+    RepositoryGroupHandler repositoryGroupHandler;
+
+    @Inject
     ProxyRegistry proxyRegistry;
 
     @Inject
@@ -143,7 +148,7 @@ public abstract class AbstractMavenRepositorySearch
         archivaConfig.addListener( EasyMock.anyObject( 
ConfigurationListener.class ) );
         EasyMock.expect( archivaConfig.getDefaultLocale() ).andReturn( 
Locale.getDefault( ) ).anyTimes();
         EasyMock.expect( archivaConfig.getConfiguration() 
).andReturn(config).anyTimes();
-        archivaConfig.save(EasyMock.anyObject(Configuration.class));
+        archivaConfig.save(EasyMock.anyObject(Configuration.class), 
EasyMock.anyString());
         EasyMock.expectLastCall().anyTimes();
         archivaConfigControl.replay();
         repositoryRegistry.reload();
@@ -158,7 +163,7 @@ public abstract class AbstractMavenRepositorySearch
         archivaConfigControl.reset();
         EasyMock.expect( archivaConfig.getDefaultLocale() ).andReturn( 
Locale.getDefault( ) ).anyTimes();
         EasyMock.expect( archivaConfig.getConfiguration() 
).andReturn(config).anyTimes();
-        archivaConfig.save(EasyMock.anyObject(Configuration.class));
+        archivaConfig.save(EasyMock.anyObject(Configuration.class), 
EasyMock.anyString());
         EasyMock.expectLastCall().anyTimes();
         archivaConfigControl.replay();
         repositoryRegistry.removeRepository(TEST_REPO_1);
@@ -259,7 +264,7 @@ public abstract class AbstractMavenRepositorySearch
         archivaConfigControl.reset();
         archivaConfig.addListener( EasyMock.anyObject( 
ConfigurationListener.class ) );
         EasyMock.expect( archivaConfig.getConfiguration() 
).andReturn(config).anyTimes();
-        archivaConfig.save(EasyMock.anyObject(Configuration.class));
+        archivaConfig.save(EasyMock.anyObject(Configuration.class), 
EasyMock.anyString());
         EasyMock.expectLastCall().anyTimes();
         archivaConfigControl.replay();
         repositoryRegistry.reload();
diff --git 
a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/search/MavenRepositorySearchOSGITest.java
 
b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/search/MavenRepositorySearchOSGITest.java
index 8fe10ea..90093ed 100644
--- 
a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/search/MavenRepositorySearchOSGITest.java
+++ 
b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/search/MavenRepositorySearchOSGITest.java
@@ -23,6 +23,7 @@ import org.apache.archiva.indexer.search.SearchFields;
 import org.apache.archiva.indexer.search.SearchResultHit;
 import org.apache.archiva.indexer.search.SearchResults;
 import org.apache.archiva.repository.base.ArchivaRepositoryRegistry;
+import org.apache.archiva.repository.base.RepositoryGroupHandler;
 import org.easymock.EasyMock;
 import org.junit.After;
 import org.junit.Test;
@@ -43,6 +44,9 @@ public class MavenRepositorySearchOSGITest
     @Inject
     ArchivaRepositoryRegistry repositoryRegistry;
 
+    @Inject
+    RepositoryGroupHandler repositoryGroupHandler;
+
     @After
     @Override
     public void tearDown() throws Exception {
diff --git 
a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/search/MavenRepositorySearchPaginateTest.java
 
b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/search/MavenRepositorySearchPaginateTest.java
index 72cc69f..f1aa3eb 100644
--- 
a/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/search/MavenRepositorySearchPaginateTest.java
+++ 
b/archiva-modules/archiva-maven/archiva-maven-indexer/src/test/java/org/apache/archiva/indexer/maven/search/MavenRepositorySearchPaginateTest.java
@@ -24,6 +24,7 @@ import org.apache.archiva.indexer.search.SearchResultLimits;
 import org.apache.archiva.indexer.search.SearchResults;
 import org.apache.archiva.indexer.util.SearchUtil;
 import org.apache.archiva.repository.base.ArchivaRepositoryRegistry;
+import org.apache.archiva.repository.base.RepositoryGroupHandler;
 import org.apache.archiva.test.utils.ArchivaSpringJUnit4ClassRunner;
 import org.junit.After;
 import org.junit.Test;
@@ -31,6 +32,7 @@ import org.junit.runner.RunWith;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.test.context.ContextConfiguration;
 
+import javax.inject.Inject;
 import java.util.Arrays;
 
 /**
@@ -42,9 +44,12 @@ public class MavenRepositorySearchPaginateTest
     extends TestCase
 {
 
-    @Autowired
+    @Inject
     ArchivaRepositoryRegistry repositoryRegistry;
 
+    @Inject
+    RepositoryGroupHandler repositoryGroupHandler;
+
     @After
     public void endTests() {
         assert repositoryRegistry!=null;
diff --git 
a/archiva-modules/archiva-maven/archiva-maven-proxy/src/test/java/org/apache/archiva/proxy/MockConfiguration.java
 
b/archiva-modules/archiva-maven/archiva-maven-proxy/src/test/java/org/apache/archiva/proxy/MockConfiguration.java
index 7e3d945..fb9fe0b 100644
--- 
a/archiva-modules/archiva-maven/archiva-maven-proxy/src/test/java/org/apache/archiva/proxy/MockConfiguration.java
+++ 
b/archiva-modules/archiva-maven/archiva-maven-proxy/src/test/java/org/apache/archiva/proxy/MockConfiguration.java
@@ -111,6 +111,12 @@ public class MockConfiguration
         /* do nothing */
     }
 
+    @Override
+    public void save( Configuration configuration, String eventTag ) throws 
RegistryException, IndeterminateConfigurationException
+    {
+        // do nothing
+    }
+
     public void triggerChange( String name, String value )
     {
         for ( org.apache.archiva.components.registry.RegistryListener listener 
: registryListeners )
diff --git 
a/archiva-modules/archiva-maven/archiva-maven-repository/src/test/java/org/apache/archiva/repository/maven/metadata/storage/mock/MockConfiguration.java
 
b/archiva-modules/archiva-maven/archiva-maven-repository/src/test/java/org/apache/archiva/repository/maven/metadata/storage/mock/MockConfiguration.java
index 018f85e..7085e96 100644
--- 
a/archiva-modules/archiva-maven/archiva-maven-repository/src/test/java/org/apache/archiva/repository/maven/metadata/storage/mock/MockConfiguration.java
+++ 
b/archiva-modules/archiva-maven/archiva-maven-repository/src/test/java/org/apache/archiva/repository/maven/metadata/storage/mock/MockConfiguration.java
@@ -27,6 +27,7 @@ import 
org.apache.archiva.configuration.ArchivaRuntimeConfiguration;
 import org.apache.archiva.configuration.Configuration;
 import org.apache.archiva.configuration.ConfigurationListener;
 import org.apache.archiva.configuration.FileType;
+import org.apache.archiva.configuration.IndeterminateConfigurationException;
 import org.apache.archiva.configuration.RepositoryScanningConfiguration;
 import org.apache.commons.lang3.StringUtils;
 import org.easymock.IMocksControl;
@@ -109,6 +110,12 @@ public class MockConfiguration
         /* do nothing */
     }
 
+    @Override
+    public void save( Configuration configuration, String eventTag ) throws 
RegistryException, IndeterminateConfigurationException
+    {
+        // do nothing
+    }
+
     public void triggerChange( String name, String value )
     {
         for(RegistryListener listener: registryListeners)
diff --git 
a/archiva-modules/archiva-maven/archiva-maven-repository/src/test/java/org/apache/archiva/repository/maven/mock/RepositoryRegistryMock.java
 
b/archiva-modules/archiva-maven/archiva-maven-repository/src/test/java/org/apache/archiva/repository/maven/mock/RepositoryRegistryMock.java
index 9966d8b..4ceed38 100644
--- 
a/archiva-modules/archiva-maven/archiva-maven-repository/src/test/java/org/apache/archiva/repository/maven/mock/RepositoryRegistryMock.java
+++ 
b/archiva-modules/archiva-maven/archiva-maven-repository/src/test/java/org/apache/archiva/repository/maven/mock/RepositoryRegistryMock.java
@@ -22,6 +22,7 @@ import org.apache.archiva.repository.ManagedRepository;
 import org.apache.archiva.repository.Repository;
 import org.apache.archiva.repository.RepositoryException;
 import org.apache.archiva.repository.base.ArchivaRepositoryRegistry;
+import org.apache.archiva.repository.base.ConfigurationHandler;
 
 import java.util.Map;
 import java.util.TreeMap;
@@ -31,6 +32,11 @@ public class RepositoryRegistryMock extends 
ArchivaRepositoryRegistry
 
     private Map<String, ManagedRepository> managedRepositories = new 
TreeMap<>();
 
+    public RepositoryRegistryMock( ConfigurationHandler configurationHandler )
+    {
+        super( configurationHandler );
+    }
+
     @Override
     public ManagedRepository putRepository(ManagedRepository 
managedRepository) throws RepositoryException
     {
diff --git 
a/archiva-modules/archiva-maven/archiva-maven-repository/src/test/java/org/apache/archiva/repository/maven/mock/configuration/StubConfiguration.java
 
b/archiva-modules/archiva-maven/archiva-maven-repository/src/test/java/org/apache/archiva/repository/maven/mock/configuration/StubConfiguration.java
index 470f24b..6a34278 100644
--- 
a/archiva-modules/archiva-maven/archiva-maven-repository/src/test/java/org/apache/archiva/repository/maven/mock/configuration/StubConfiguration.java
+++ 
b/archiva-modules/archiva-maven/archiva-maven-repository/src/test/java/org/apache/archiva/repository/maven/mock/configuration/StubConfiguration.java
@@ -57,6 +57,12 @@ public class StubConfiguration
     }
 
     @Override
+    public void save( Configuration configuration, String eventTag ) throws 
RegistryException, IndeterminateConfigurationException
+    {
+        this.configuration = configuration;
+    }
+
+    @Override
     public boolean isDefaulted()
     {
         return false;
diff --git 
a/archiva-modules/archiva-maven/archiva-maven-repository/src/test/java/org/apache/archiva/repository/maven/mock/configuration/TestConfiguration.java
 
b/archiva-modules/archiva-maven/archiva-maven-repository/src/test/java/org/apache/archiva/repository/maven/mock/configuration/TestConfiguration.java
index 9914da9..78625d7 100644
--- 
a/archiva-modules/archiva-maven/archiva-maven-repository/src/test/java/org/apache/archiva/repository/maven/mock/configuration/TestConfiguration.java
+++ 
b/archiva-modules/archiva-maven/archiva-maven-repository/src/test/java/org/apache/archiva/repository/maven/mock/configuration/TestConfiguration.java
@@ -52,6 +52,12 @@ public class TestConfiguration
     }
 
     @Override
+    public void save( Configuration configuration, String eventTag ) throws 
RegistryException, IndeterminateConfigurationException
+    {
+        this.configuration = configuration;
+    }
+
+    @Override
     public boolean isDefaulted()
     {
         return false;
diff --git 
a/archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/java/org/apache/archiva/scheduler/indexing/maven/ArchivaIndexingTaskExecutorTest.java
 
b/archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/java/org/apache/archiva/scheduler/indexing/maven/ArchivaIndexingTaskExecutorTest.java
index 450ebd3..d778d5c 100644
--- 
a/archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/java/org/apache/archiva/scheduler/indexing/maven/ArchivaIndexingTaskExecutorTest.java
+++ 
b/archiva-modules/archiva-maven/archiva-maven-scheduler/src/test/java/org/apache/archiva/scheduler/indexing/maven/ArchivaIndexingTaskExecutorTest.java
@@ -26,6 +26,7 @@ import 
org.apache.archiva.repository.base.ArchivaRepositoryRegistry;
 import org.apache.archiva.repository.base.BasicManagedRepository;
 import org.apache.archiva.repository.ManagedRepository;
 import org.apache.archiva.repository.ReleaseScheme;
+import org.apache.archiva.repository.base.RepositoryGroupHandler;
 import org.apache.archiva.repository.storage.StorageAsset;
 import org.apache.archiva.repository.features.IndexCreationFeature;
 import org.apache.archiva.scheduler.indexing.ArtifactIndexingTask;
@@ -74,6 +75,9 @@ public class ArchivaIndexingTaskExecutorTest
     ArchivaRepositoryRegistry repositoryRegistry;
 
     @Inject
+    RepositoryGroupHandler repositoryGroupHandler;
+
+    @Inject
     private IndexUpdater indexUpdater;
 
     private ManagedRepository repo;
diff --git 
a/archiva-modules/archiva-scheduler/archiva-scheduler-repository/src/test/java/org/apache/archiva/scheduler/repository/AbstractArchivaRepositoryScanningTaskExecutorTest.java
 
b/archiva-modules/archiva-scheduler/archiva-scheduler-repository/src/test/java/org/apache/archiva/scheduler/repository/AbstractArchivaRepositoryScanningTaskExecutorTest.java
index 1a6d390..443078e 100644
--- 
a/archiva-modules/archiva-scheduler/archiva-scheduler-repository/src/test/java/org/apache/archiva/scheduler/repository/AbstractArchivaRepositoryScanningTaskExecutorTest.java
+++ 
b/archiva-modules/archiva-scheduler/archiva-scheduler-repository/src/test/java/org/apache/archiva/scheduler/repository/AbstractArchivaRepositoryScanningTaskExecutorTest.java
@@ -29,6 +29,7 @@ import org.apache.archiva.mock.MockRepositorySessionFactory;
 import org.apache.archiva.components.taskqueue.execution.TaskExecutor;
 import org.apache.archiva.repository.ManagedRepository;
 import org.apache.archiva.repository.RepositoryRegistry;
+import org.apache.archiva.repository.base.RepositoryGroupHandler;
 import org.apache.archiva.scheduler.repository.model.RepositoryTask;
 import org.apache.archiva.test.utils.ArchivaSpringJUnit4ClassRunner;
 import org.junit.After;
@@ -66,6 +67,9 @@ public abstract class 
AbstractArchivaRepositoryScanningTaskExecutorTest
     RepositoryRegistry repositoryRegistry;
 
     @Inject
+    RepositoryGroupHandler groupHandler;
+
+    @Inject
     @Named( value = "taskExecutor#test-repository-scanning" )
     protected TaskExecutor<RepositoryTask> taskExecutor;
 
diff --git 
a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/RepositoryGroupService.java
 
b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/RepositoryGroupService.java
index 8a5b6ea..a95c68f 100644
--- 
a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/RepositoryGroupService.java
+++ 
b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/RepositoryGroupService.java
@@ -55,20 +55,20 @@ import static 
org.apache.archiva.rest.api.services.v2.RestConfiguration.DEFAULT_
  * @since 3.0
  */
 @Path( "/repository_groups" )
-@Schema( name="RepositoryGroups", description = "Managing of repository groups 
or virtual repositories")
+@Schema( name = "RepositoryGroups", description = "Managing of repository 
groups or virtual repositories" )
 public interface RepositoryGroupService
 {
     @Path( "" )
     @GET
-    @Produces( { APPLICATION_JSON } )
+    @Produces( {APPLICATION_JSON} )
     @RedbackAuthorization( permissions = 
ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION )
     @Operation( summary = "Returns all repository group entries.",
         parameters = {
-            @Parameter(name = "q", description = "Search term"),
-            @Parameter(name = "offset", description = "The offset of the first 
element returned"),
-            @Parameter(name = "limit", description = "Maximum number of items 
to return in the response"),
-            @Parameter(name = "orderBy", description = "List of attribute used 
for sorting (key, value)"),
-            @Parameter(name = "order", description = "The sort order. Either 
ascending (asc) or descending (desc)")
+            @Parameter( name = "q", description = "Search term" ),
+            @Parameter( name = "offset", description = "The offset of the 
first element returned" ),
+            @Parameter( name = "limit", description = "Maximum number of items 
to return in the response" ),
+            @Parameter( name = "orderBy", description = "List of attribute 
used for sorting (key, value)" ),
+            @Parameter( name = "order", description = "The sort order. Either 
ascending (asc) or descending (desc)" )
         },
         security = {
             @SecurityRequirement(
@@ -78,22 +78,22 @@ public interface RepositoryGroupService
         responses = {
             @ApiResponse( responseCode = "200",
                 description = "If the list could be returned",
-                content = @Content(mediaType = APPLICATION_JSON, schema = 
@Schema(implementation = PagedResult.class))
+                content = @Content( mediaType = APPLICATION_JSON, schema = 
@Schema( implementation = PagedResult.class ) )
             ),
             @ApiResponse( responseCode = "403", description = "Authenticated 
user is not permitted to gather the information",
-                content = @Content(mediaType = APPLICATION_JSON, schema = 
@Schema(implementation = ArchivaRestError.class )) )
+                content = @Content( mediaType = APPLICATION_JSON, schema = 
@Schema( implementation = ArchivaRestError.class ) ) )
         }
     )
-    PagedResult<RepositoryGroup> getRepositoriesGroups(@QueryParam("q") 
@DefaultValue( "" ) String searchTerm,
-                                                       @QueryParam( "offset" ) 
@DefaultValue( "0" ) Integer offset,
-                                                       @QueryParam( "limit" ) 
@DefaultValue( value = DEFAULT_PAGE_LIMIT ) Integer limit,
-                                                       @QueryParam( "orderBy") 
@DefaultValue( "id" ) List<String> orderBy,
-                                                       @QueryParam("order") 
@DefaultValue( "asc" ) String order)
+    PagedResult<RepositoryGroup> getRepositoriesGroups( @QueryParam( "q" ) 
@DefaultValue( "" ) String searchTerm,
+                                                        @QueryParam( "offset" 
) @DefaultValue( "0" ) Integer offset,
+                                                        @QueryParam( "limit" ) 
@DefaultValue( value = DEFAULT_PAGE_LIMIT ) Integer limit,
+                                                        @QueryParam( "orderBy" 
) @DefaultValue( "id" ) List<String> orderBy,
+                                                        @QueryParam( "order" ) 
@DefaultValue( "asc" ) String order )
         throws ArchivaRestServiceException;
 
     @Path( "{repositoryGroupId}" )
     @GET
-    @Produces( { APPLICATION_JSON } )
+    @Produces( {APPLICATION_JSON} )
     @RedbackAuthorization( permissions = 
ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION )
     @Operation( summary = "Returns a single repository group configuration.",
         security = {
@@ -104,12 +104,12 @@ public interface RepositoryGroupService
         responses = {
             @ApiResponse( responseCode = "200",
                 description = "If the configuration is returned",
-                content = @Content(mediaType = APPLICATION_JSON, schema = 
@Schema(implementation = RepositoryGroup.class))
+                content = @Content( mediaType = APPLICATION_JSON, schema = 
@Schema( implementation = RepositoryGroup.class ) )
             ),
             @ApiResponse( responseCode = "403", description = "Authenticated 
user is not permitted to gather the information",
-                content = @Content(mediaType = APPLICATION_JSON, schema = 
@Schema(implementation = ArchivaRestError.class )) ),
+                content = @Content( mediaType = APPLICATION_JSON, schema = 
@Schema( implementation = ArchivaRestError.class ) ) ),
             @ApiResponse( responseCode = "404", description = "The repository 
group with the given id does not exist",
-                content = @Content(mediaType = APPLICATION_JSON, schema = 
@Schema(implementation = ArchivaRestError.class )) )
+                content = @Content( mediaType = APPLICATION_JSON, schema = 
@Schema( implementation = ArchivaRestError.class ) ) )
         }
     )
     RepositoryGroup getRepositoryGroup( @PathParam( "repositoryGroupId" ) 
String repositoryGroupId )
@@ -117,14 +117,14 @@ public interface RepositoryGroupService
 
     @Path( "" )
     @POST
-    @Consumes( { APPLICATION_JSON } )
-    @Produces( { APPLICATION_JSON } )
+    @Consumes( {APPLICATION_JSON} )
+    @Produces( {APPLICATION_JSON} )
     @RedbackAuthorization( permissions = 
ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION )
     @Operation( summary = "Creates a new group entry.",
         requestBody =
-            @RequestBody(required = true, description = "The configuration of 
the repository group.",
-                content = @Content(mediaType = APPLICATION_JSON, schema = 
@Schema(implementation = RepositoryGroup.class))
-            )
+        @RequestBody( required = true, description = "The configuration of the 
repository group.",
+            content = @Content( mediaType = APPLICATION_JSON, schema = 
@Schema( implementation = RepositoryGroup.class ) )
+        )
         ,
         security = {
             @SecurityRequirement(
@@ -134,17 +134,17 @@ public interface RepositoryGroupService
         responses = {
             @ApiResponse( responseCode = "201",
                 description = "If the list could be returned",
-                content = @Content(mediaType = APPLICATION_JSON, schema = 
@Schema(implementation = RepositoryGroup.class))
+                content = @Content( mediaType = APPLICATION_JSON, schema = 
@Schema( implementation = RepositoryGroup.class ) )
             ),
             @ApiResponse( responseCode = "303", description = "The repository 
group exists already",
                 headers = {
-                    @Header( name="Location", description = "The URL of 
existing group", schema = @Schema(type="string"))
+                    @Header( name = "Location", description = "The URL of 
existing group", schema = @Schema( type = "string" ) )
                 }
             ),
             @ApiResponse( responseCode = "403", description = "Authenticated 
user is not permitted to gather the information",
-                content = @Content(mediaType = APPLICATION_JSON, schema = 
@Schema(implementation = ArchivaRestError.class )) ),
+                content = @Content( mediaType = APPLICATION_JSON, schema = 
@Schema( implementation = ArchivaRestError.class ) ) ),
             @ApiResponse( responseCode = "422", description = "The body data 
is not valid",
-                content = @Content(mediaType = APPLICATION_JSON, schema = 
@Schema(implementation = ArchivaRestError.class )) )
+                content = @Content( mediaType = APPLICATION_JSON, schema = 
@Schema( implementation = ArchivaRestError.class ) ) )
         }
     )
     RepositoryGroup addRepositoryGroup( RepositoryGroup repositoryGroup )
@@ -152,13 +152,13 @@ public interface RepositoryGroupService
 
     @Path( "{repositoryGroupId}" )
     @PUT
-    @Consumes( { APPLICATION_JSON } )
-    @Produces( { APPLICATION_JSON } )
+    @Consumes( {APPLICATION_JSON} )
+    @Produces( {APPLICATION_JSON} )
     @RedbackAuthorization( permissions = 
ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION )
     @Operation( summary = "Returns all repository group entries.",
         requestBody =
-        @RequestBody(required = true, description = "The configuration of the 
repository group.",
-            content = @Content(mediaType = APPLICATION_JSON, schema = 
@Schema(implementation = RepositoryGroup.class))
+        @RequestBody( required = true, description = "The configuration of the 
repository group.",
+            content = @Content( mediaType = APPLICATION_JSON, schema = 
@Schema( implementation = RepositoryGroup.class ) )
         )
         ,
         security = {
@@ -169,14 +169,14 @@ public interface RepositoryGroupService
         responses = {
             @ApiResponse( responseCode = "200",
                 description = "If the group is returned",
-                content = @Content(mediaType = APPLICATION_JSON, schema = 
@Schema(implementation = RepositoryGroup.class))
+                content = @Content( mediaType = APPLICATION_JSON, schema = 
@Schema( implementation = RepositoryGroup.class ) )
             ),
             @ApiResponse( responseCode = "403", description = "Authenticated 
user is not permitted to gather the information",
-                content = @Content(mediaType = APPLICATION_JSON, schema = 
@Schema(implementation = ArchivaRestError.class )) ),
+                content = @Content( mediaType = APPLICATION_JSON, schema = 
@Schema( implementation = ArchivaRestError.class ) ) ),
             @ApiResponse( responseCode = "404", description = "The group with 
the given id does not exist",
-                content = @Content(mediaType = APPLICATION_JSON, schema = 
@Schema(implementation = ArchivaRestError.class )) ),
+                content = @Content( mediaType = APPLICATION_JSON, schema = 
@Schema( implementation = ArchivaRestError.class ) ) ),
             @ApiResponse( responseCode = "422", description = "The body data 
is not valid",
-                content = @Content(mediaType = APPLICATION_JSON, schema = 
@Schema(implementation = ArchivaRestError.class )) )
+                content = @Content( mediaType = APPLICATION_JSON, schema = 
@Schema( implementation = ArchivaRestError.class ) ) )
         }
     )
     RepositoryGroup updateRepositoryGroup( @PathParam( "repositoryGroupId" ) 
String groupId, RepositoryGroup repositoryGroup )
@@ -184,7 +184,7 @@ public interface RepositoryGroupService
 
     @Path( "{repositoryGroupId}" )
     @DELETE
-    @Produces( { APPLICATION_JSON } )
+    @Produces( {APPLICATION_JSON} )
     @RedbackAuthorization( permissions = 
ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION )
     @Operation( summary = "Deletes the repository group entry with the given 
id.",
         security = {
@@ -197,9 +197,9 @@ public interface RepositoryGroupService
                 description = "If the group was deleted"
             ),
             @ApiResponse( responseCode = "403", description = "Authenticated 
user is not permitted to delete the group",
-                content = @Content(mediaType = APPLICATION_JSON, schema = 
@Schema(implementation = ArchivaRestError.class )) ),
+                content = @Content( mediaType = APPLICATION_JSON, schema = 
@Schema( implementation = ArchivaRestError.class ) ) ),
             @ApiResponse( responseCode = "404", description = "The group with 
the given id does not exist",
-                content = @Content(mediaType = APPLICATION_JSON, schema = 
@Schema(implementation = ArchivaRestError.class )) ),
+                content = @Content( mediaType = APPLICATION_JSON, schema = 
@Schema( implementation = ArchivaRestError.class ) ) ),
         }
     )
     Response deleteRepositoryGroup( @PathParam( "repositoryGroupId" ) String 
repositoryGroupId )
@@ -207,7 +207,7 @@ public interface RepositoryGroupService
 
     @Path( "{repositoryGroupId}/repositories/{repositoryId}" )
     @PUT
-    @Produces( { APPLICATION_JSON } )
+    @Produces( {APPLICATION_JSON} )
     @RedbackAuthorization( permissions = 
ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION )
     @Operation( summary = "Adds the repository with the given id to the 
repository group.",
         security = {
@@ -220,18 +220,18 @@ public interface RepositoryGroupService
                 description = "If the repository was added or if it was 
already part of the group"
             ),
             @ApiResponse( responseCode = "403", description = "Authenticated 
user is not permitted to delete the group",
-                content = @Content(mediaType = APPLICATION_JSON, schema = 
@Schema(implementation = ArchivaRestError.class )) ),
+                content = @Content( mediaType = APPLICATION_JSON, schema = 
@Schema( implementation = ArchivaRestError.class ) ) ),
             @ApiResponse( responseCode = "404", description = "The group with 
the given id does not exist",
-                content = @Content(mediaType = APPLICATION_JSON, schema = 
@Schema(implementation = ArchivaRestError.class )) ),
+                content = @Content( mediaType = APPLICATION_JSON, schema = 
@Schema( implementation = ArchivaRestError.class ) ) ),
         }
     )
     RepositoryGroup addRepositoryToGroup( @PathParam( "repositoryGroupId" ) 
String repositoryGroupId,
-                                  @PathParam( "repositoryId" ) String 
repositoryId )
+                                          @PathParam( "repositoryId" ) String 
repositoryId )
         throws ArchivaRestServiceException;
 
     @Path( "{repositoryGroupId}/repositories/{repositoryId}" )
     @DELETE
-    @Produces( { APPLICATION_JSON } )
+    @Produces( {APPLICATION_JSON} )
     @RedbackAuthorization( permissions = 
ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION )
     @Operation( summary = "Removes the repository with the given id from the 
repository group.",
         security = {
@@ -244,13 +244,13 @@ public interface RepositoryGroupService
                 description = "If the repository was removed."
             ),
             @ApiResponse( responseCode = "403", description = "Authenticated 
user is not permitted to delete the group",
-                content = @Content(mediaType = APPLICATION_JSON, schema = 
@Schema(implementation = ArchivaRestError.class )) ),
+                content = @Content( mediaType = APPLICATION_JSON, schema = 
@Schema( implementation = ArchivaRestError.class ) ) ),
             @ApiResponse( responseCode = "404", description = "The group with 
the given id does not exist, or the repository was not part of the group.",
-                content = @Content(mediaType = APPLICATION_JSON, schema = 
@Schema(implementation = ArchivaRestError.class )) ),
+                content = @Content( mediaType = APPLICATION_JSON, schema = 
@Schema( implementation = ArchivaRestError.class ) ) ),
         }
     )
     RepositoryGroup deleteRepositoryFromGroup( @PathParam( "repositoryGroupId" 
) String repositoryGroupId,
-                                       @PathParam( "repositoryId" ) String 
repositoryId )
+                                               @PathParam( "repositoryId" ) 
String repositoryId )
         throws ArchivaRestServiceException;
 
 
diff --git 
a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/MavenManagedRepositoryService.java
 
b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/maven/MavenManagedRepositoryService.java
similarity index 98%
rename from 
archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/MavenManagedRepositoryService.java
rename to 
archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/maven/MavenManagedRepositoryService.java
index 5aff95e..5e63682 100644
--- 
a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/MavenManagedRepositoryService.java
+++ 
b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/maven/MavenManagedRepositoryService.java
@@ -1,4 +1,4 @@
-package org.apache.archiva.rest.api.services.v2;
+package org.apache.archiva.rest.api.services.v2.maven;
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -31,6 +31,8 @@ import 
org.apache.archiva.redback.authorization.RedbackAuthorization;
 import org.apache.archiva.rest.api.model.v2.FileInfo;
 import org.apache.archiva.rest.api.model.v2.MavenManagedRepository;
 import org.apache.archiva.rest.api.model.v2.MavenManagedRepositoryUpdate;
+import org.apache.archiva.rest.api.services.v2.ArchivaRestError;
+import org.apache.archiva.rest.api.services.v2.ArchivaRestServiceException;
 import org.apache.archiva.security.common.ArchivaRoleConstants;
 
 import javax.ws.rs.Consumes;
@@ -51,6 +53,8 @@ import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
 import static 
org.apache.archiva.rest.api.services.v2.RestConfiguration.DEFAULT_PAGE_LIMIT;
 
 /**
+ * Service interface for managing managed maven repositories
+ *
  * @author Martin Stockhammer <[email protected]>
  * @since 3.0
  */
diff --git 
a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/DefaultRepositoriesService.java
 
b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/DefaultRepositoriesService.java
index f99b06d..06c2e4e 100644
--- 
a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/DefaultRepositoriesService.java
+++ 
b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/DefaultRepositoriesService.java
@@ -47,6 +47,7 @@ import org.apache.archiva.redback.system.SecuritySystem;
 import org.apache.archiva.redback.users.User;
 import org.apache.archiva.redback.users.UserManagerException;
 import org.apache.archiva.redback.users.UserNotFoundException;
+import org.apache.archiva.repository.base.RepositoryGroupHandler;
 import org.apache.archiva.repository.content.BaseRepositoryContentLayout;
 import org.apache.archiva.repository.content.ContentNotFoundException;
 import org.apache.archiva.repository.content.LayoutException;
@@ -128,6 +129,10 @@ public class DefaultRepositoriesService
     @Inject
     private RepositoryRegistry repositoryRegistry;
 
+    @SuppressWarnings( "unused" )
+    @Inject
+    private RepositoryGroupHandler repositoryGroupHandler;
+
     @Inject
     private SecuritySystem securitySystem;
 
diff --git 
a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/utils/AuditHelper.java
 
b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/utils/AuditHelper.java
new file mode 100644
index 0000000..b6b4aca
--- /dev/null
+++ 
b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/utils/AuditHelper.java
@@ -0,0 +1,40 @@
+package org.apache.archiva.rest.services.utils;
+/*
+ * 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;
+
+/**
+ * @author Martin Stockhammer <[email protected]>
+ */
+public class AuditHelper
+{
+    private static final AuditInformation NULL_RESULT = new AuditInformation( 
null, null );
+    public static AuditInformation getAuditData() {
+        RedbackRequestInformation redbackRequestInformation = 
RedbackAuthenticationThreadLocal.get( );
+        if (redbackRequestInformation==null) {
+            return NULL_RESULT;
+        } else
+        {
+            return new AuditInformation( redbackRequestInformation.getUser( ), 
redbackRequestInformation.getRemoteAddr( ) );
+        }
+    }
+}
diff --git 
a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/v2/DefaultMavenManagedRepositoryService.java
 
b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/v2/DefaultMavenManagedRepositoryService.java
index 6fd85d5..7b776a7 100644
--- 
a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/v2/DefaultMavenManagedRepositoryService.java
+++ 
b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/v2/DefaultMavenManagedRepositoryService.java
@@ -35,35 +35,28 @@ 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;
 import org.apache.archiva.repository.content.LayoutException;
-import org.apache.archiva.repository.storage.fs.FilesystemStorage;
 import org.apache.archiva.repository.storage.fs.FsStorageUtil;
-import org.apache.archiva.repository.storage.util.StorageUtil;
-import org.apache.archiva.rest.api.model.v2.Artifact;
 import org.apache.archiva.rest.api.model.v2.FileInfo;
 import org.apache.archiva.rest.api.model.v2.MavenManagedRepository;
 import org.apache.archiva.rest.api.model.v2.MavenManagedRepositoryUpdate;
 import org.apache.archiva.rest.api.services.v2.ArchivaRestServiceException;
 import org.apache.archiva.rest.api.services.v2.ErrorKeys;
 import org.apache.archiva.rest.api.services.v2.ErrorMessage;
-import org.apache.archiva.rest.api.services.v2.MavenManagedRepositoryService;
+import 
org.apache.archiva.rest.api.services.v2.maven.MavenManagedRepositoryService;
 import org.apache.archiva.security.common.ArchivaRoleConstants;
 import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
 
-import javax.inject.Inject;
 import javax.servlet.http.HttpServletResponse;
-import javax.ws.rs.PathParam;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
-import java.io.File;
 import java.io.IOException;
 import java.util.Collection;
 import java.util.Comparator;
@@ -103,7 +96,9 @@ public class DefaultMavenManagedRepositoryService implements 
MavenManagedReposit
     private RepositoryRegistry repositoryRegistry;
     private SecuritySystem securitySystem;
 
-    public DefaultMavenManagedRepositoryService( SecuritySystem 
securitySystem, RepositoryRegistry repositoryRegistry, ManagedRepositoryAdmin 
managedRepositoryAdmin )
+    public DefaultMavenManagedRepositoryService( SecuritySystem securitySystem,
+                                                 RepositoryRegistry 
repositoryRegistry,
+                                                 ManagedRepositoryAdmin 
managedRepositoryAdmin )
     {
         this.securitySystem = securitySystem;
         this.repositoryRegistry = repositoryRegistry;
@@ -113,8 +108,16 @@ public class DefaultMavenManagedRepositoryService 
implements MavenManagedReposit
     protected AuditInformation getAuditInformation( )
     {
         RedbackRequestInformation redbackRequestInformation = 
RedbackAuthenticationThreadLocal.get( );
-        User user = redbackRequestInformation == null ? null : 
redbackRequestInformation.getUser( );
-        String remoteAddr = redbackRequestInformation == null ? null : 
redbackRequestInformation.getRemoteAddr( );
+        User user;
+        String remoteAddr;
+        if (redbackRequestInformation==null) {
+            user = null;
+            remoteAddr = null;
+        } 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/services/v2/DefaultRepositoryGroupService.java
 
b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/v2/DefaultRepositoryGroupService.java
index 9290873..44ce638 100644
--- 
a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/v2/DefaultRepositoryGroupService.java
+++ 
b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/v2/DefaultRepositoryGroupService.java
@@ -34,16 +34,12 @@ package org.apache.archiva.rest.services.v2;/*
  * under the License.
  */
 
-import org.apache.archiva.admin.model.AuditInformation;
 import org.apache.archiva.admin.model.EntityExistsException;
 import org.apache.archiva.admin.model.EntityNotFoundException;
 import org.apache.archiva.admin.model.RepositoryAdminException;
 import org.apache.archiva.admin.model.group.RepositoryGroupAdmin;
 import org.apache.archiva.components.rest.model.PagedResult;
 import org.apache.archiva.components.rest.util.QueryHelper;
-import 
org.apache.archiva.redback.rest.services.RedbackAuthenticationThreadLocal;
-import org.apache.archiva.redback.rest.services.RedbackRequestInformation;
-import org.apache.archiva.redback.users.User;
 import org.apache.archiva.rest.api.model.v2.RepositoryGroup;
 import org.apache.archiva.rest.api.services.v2.ArchivaRestServiceException;
 import org.apache.archiva.rest.api.services.v2.ErrorKeys;
@@ -54,7 +50,6 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
 
-import javax.inject.Inject;
 import javax.servlet.http.HttpServletResponse;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.Response;
@@ -65,6 +60,8 @@ import java.util.List;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
+import static org.apache.archiva.rest.services.utils.AuditHelper.getAuditData;
+
 /**
  * REST V2 Implementation for repository groups.
  *
@@ -81,8 +78,7 @@ public class DefaultRepositoryGroupService implements 
RepositoryGroupService
     @Context
     UriInfo uriInfo;
 
-    @Inject
-    private RepositoryGroupAdmin repositoryGroupAdmin;
+    final private RepositoryGroupAdmin repositoryGroupAdmin;
 
     private static final Logger log = LoggerFactory.getLogger( 
DefaultRepositoryGroupService.class );
     private static final 
QueryHelper<org.apache.archiva.admin.model.beans.RepositoryGroup> QUERY_HELPER 
= new QueryHelper<>( new String[]{"id"} );
@@ -93,12 +89,8 @@ public class DefaultRepositoryGroupService implements 
RepositoryGroupService
     }
 
 
-    protected AuditInformation getAuditInformation( )
-    {
-        RedbackRequestInformation redbackRequestInformation = 
RedbackAuthenticationThreadLocal.get( );
-        User user = redbackRequestInformation == null ? null : 
redbackRequestInformation.getUser( );
-        String remoteAddr = redbackRequestInformation == null ? null : 
redbackRequestInformation.getRemoteAddr( );
-        return new AuditInformation( user, remoteAddr );
+    public DefaultRepositoryGroupService(RepositoryGroupAdmin 
repositoryGroupAdmin) {
+        this.repositoryGroupAdmin = repositoryGroupAdmin;
     }
 
     @Override
@@ -166,7 +158,7 @@ public class DefaultRepositoryGroupService implements 
RepositoryGroupService
     {
         try
         {
-            Boolean result = repositoryGroupAdmin.addRepositoryGroup( toModel( 
repositoryGroup ), getAuditInformation( ) );
+            Boolean result = repositoryGroupAdmin.addRepositoryGroup( toModel( 
repositoryGroup ), getAuditData() );
             if ( result )
             {
                 org.apache.archiva.admin.model.beans.RepositoryGroup newGroup 
= repositoryGroupAdmin.getRepositoryGroup( repositoryGroup.getId( ) );
@@ -243,7 +235,7 @@ public class DefaultRepositoryGroupService implements 
RepositoryGroupService
             {
                 updateGroup.setMergedIndexTtl( originGroup.getMergedIndexTtl( 
) );
             }
-            repositoryGroupAdmin.updateRepositoryGroup( updateGroup, 
getAuditInformation( ) );
+            repositoryGroupAdmin.updateRepositoryGroup( updateGroup, 
getAuditData( ) );
             return RepositoryGroup.of( 
repositoryGroupAdmin.getRepositoryGroup( repositoryGroupId ) );
         }
         catch ( EntityNotFoundException e )
@@ -265,7 +257,7 @@ public class DefaultRepositoryGroupService implements 
RepositoryGroupService
         }
         try
         {
-            Boolean deleted = repositoryGroupAdmin.deleteRepositoryGroup( 
repositoryGroupId, getAuditInformation( ) );
+            Boolean deleted = repositoryGroupAdmin.deleteRepositoryGroup( 
repositoryGroupId, getAuditData( ) );
             if ( !deleted )
             {
                 throw new ArchivaRestServiceException( ErrorMessage.of( 
ErrorKeys.REPOSITORY_GROUP_DELETE_FAILED ) );
@@ -297,7 +289,7 @@ public class DefaultRepositoryGroupService implements 
RepositoryGroupService
         }
         try
         {
-            repositoryGroupAdmin.addRepositoryToGroup( repositoryGroupId, 
repositoryId, getAuditInformation( ) );
+            repositoryGroupAdmin.addRepositoryToGroup( repositoryGroupId, 
repositoryId, getAuditData( ) );
             return RepositoryGroup.of( 
repositoryGroupAdmin.getRepositoryGroup( repositoryGroupId ) );
         }
         catch ( EntityNotFoundException e )
@@ -335,7 +327,7 @@ public class DefaultRepositoryGroupService implements 
RepositoryGroupService
         }
         try
         {
-            repositoryGroupAdmin.deleteRepositoryFromGroup( repositoryGroupId, 
repositoryId, getAuditInformation( ) );
+            repositoryGroupAdmin.deleteRepositoryFromGroup( repositoryGroupId, 
repositoryId, getAuditData( ) );
             return RepositoryGroup.of( 
repositoryGroupAdmin.getRepositoryGroup( repositoryGroupId ) );
         }
         catch ( EntityNotFoundException e )
diff --git 
a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/resources/META-INF/spring-context.xml
 
b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/resources/META-INF/spring-context.xml
index 53b445a..46dbc49 100644
--- 
a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/resources/META-INF/spring-context.xml
+++ 
b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/resources/META-INF/spring-context.xml
@@ -29,11 +29,8 @@
            http://cxf.apache.org/jaxrs
            http://cxf.apache.org/schemas/jaxrs.xsd"; default-lazy-init="true">
 
-  <import resource="classpath:META-INF/cxf/cxf.xml"/>
-  <!--
-  <import resource="classpath:META-INF/cxf/cxf-extension-jaxrs-binding.xml"/>
-  -->
-  <import resource="classpath:META-INF/cxf/cxf-servlet.xml"/>
+  <import resource="classpath*:META-INF/cxf/cxf.xml"/>
+  <import resource="classpath*:META-INF/cxf/cxf-servlet.xml"/>
 
   <context:annotation-config/>
   <context:component-scan
@@ -119,6 +116,7 @@
       <ref bean="v2.defaultSecurityConfigurationService" />
       <ref bean="v2.repositoryGroupService#rest" />
       <ref bean="v2.repositoryService#rest"/>
+      <ref bean="v2.managedMavenRepositoryService#rest"/>
     </jaxrs:serviceBeans>
 
     <jaxrs:features>
diff --git 
a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/java/org/apache/archiva/rest/services/v2/AbstractNativeRestServices.java
 
b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/java/org/apache/archiva/rest/services/v2/AbstractNativeRestServices.java
index 06d6f74..18093fa 100644
--- 
a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/java/org/apache/archiva/rest/services/v2/AbstractNativeRestServices.java
+++ 
b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/java/org/apache/archiva/rest/services/v2/AbstractNativeRestServices.java
@@ -378,7 +378,7 @@ public abstract class AbstractNativeRestServices
         }
         else
         {
-            log.error( "Serer is not in STARTED state!" );
+            log.error( "Server is not in STARTED state!" );
         }
     }
 
@@ -410,15 +410,11 @@ public abstract class AbstractNativeRestServices
         this.requestSpec = getRequestSpecBuilder( ).build( );
         RestAssured.basePath = basePath;
         RestAssured.config = RestAssuredConfig.config().objectMapperConfig(new 
ObjectMapperConfig().jackson2ObjectMapperFactory(
-            new Jackson2ObjectMapperFactory() {
-                @Override
-                public ObjectMapper create( Type cls, String charset) {
-                    ObjectMapper om = new 
ObjectMapper().findAndRegisterModules();
-                    om.configure( 
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
-                    om.setPropertyNamingStrategy( 
PropertyNamingStrategy.SNAKE_CASE );
-                    return om;
-                }
-
+            ( cls, charset ) -> {
+                ObjectMapper om = new ObjectMapper().findAndRegisterModules();
+                om.configure( 
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+                om.setPropertyNamingStrategy( 
PropertyNamingStrategy.SNAKE_CASE );
+                return om;
             }
         ));
     }
diff --git 
a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/java/org/apache/archiva/rest/services/v2/NativeMavenManagedRepositoryServiceTest.java
 
b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/java/org/apache/archiva/rest/services/v2/NativeMavenManagedRepositoryServiceTest.java
new file mode 100644
index 0000000..f7d137f
--- /dev/null
+++ 
b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/java/org/apache/archiva/rest/services/v2/NativeMavenManagedRepositoryServiceTest.java
@@ -0,0 +1,97 @@
+package org.apache.archiva.rest.services.v2;
+
+/*
+ * 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 io.restassured.path.json.JsonPath;
+import io.restassured.response.Response;
+import io.restassured.response.ResponseBody;
+import org.apache.archiva.components.rest.model.PagedResult;
+import org.apache.archiva.components.rest.model.PropertyEntry;
+import org.apache.archiva.rest.api.model.v2.BeanInformation;
+import org.apache.archiva.rest.api.model.v2.CacheConfiguration;
+import org.apache.archiva.rest.api.model.v2.LdapConfiguration;
+import org.apache.archiva.rest.api.model.v2.MavenManagedRepository;
+import org.apache.archiva.rest.api.services.v2.RestConfiguration;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.TestMethodOrder;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static io.restassured.RestAssured.given;
+import static io.restassured.http.ContentType.JSON;
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * @author Martin Stockhammer <[email protected]>
+ */
+@TestInstance( TestInstance.Lifecycle.PER_CLASS )
+@Tag( "rest-native" )
+@TestMethodOrder( MethodOrderer.OrderAnnotation.class )
+@DisplayName( "Native REST tests for V2 ManagedRepositoryService" )
+public class NativeMavenManagedRepositoryServiceTest extends 
AbstractNativeRestServices
+{
+    @Override
+    protected String getServicePath( )
+    {
+        return "/repositories/maven/managed";
+    }
+
+    @BeforeAll
+    void setup( ) throws Exception
+    {
+        super.setupNative( );
+    }
+
+    @AfterAll
+    void destroy( ) throws Exception
+    {
+        super.shutdownNative( );
+    }
+
+    @Test
+    @Order( 1 )
+    void testGetRepositories( )
+    {
+        String token = getAdminToken( );
+        Response response = given( ).spec( getRequestSpec( token ) 
).contentType( JSON )
+            .when( )
+            .get( "" )
+            .then( ).statusCode( 200 ).extract( ).response( );
+        JsonPath json = response.getBody( ).jsonPath( );
+        assertEquals( 2, json.getInt( "pagination.total_count" ) );
+        assertEquals( 0, json.getInt( "pagination.offset" ) );
+        assertEquals( Integer.valueOf( RestConfiguration.DEFAULT_PAGE_LIMIT ), 
json.getInt( "pagination.limit" ) );
+        List<MavenManagedRepository> repositories = json.getList( "data", 
MavenManagedRepository.class );
+        assertEquals( "internal", repositories.get( 0 ).getId( ) );
+        assertEquals( "snapshots", repositories.get( 1 ).getId( ) );
+    }
+
+
+
+}
diff --git 
a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/resources/META-INF/spring-context-test.xml
 
b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/resources/META-INF/spring-context-test.xml
index bec39de..19eba02 100644
--- 
a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/resources/META-INF/spring-context-test.xml
+++ 
b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/resources/META-INF/spring-context-test.xml
@@ -32,7 +32,7 @@
 
   <context:annotation-config/>
   <context:component-scan
-      
base-package="org.apache.archiva.redback.keys,org.apache.archiva.rest.services.utils,org.apache.archiva.repository.content.maven2"/>
+      
base-package="org.apache.archiva.redback.keys,org.apache.archiva.rest.services.utils,org.apache.archiva.repository.maven.content"/>
   
   <bean name="scheduler" 
class="org.apache.archiva.components.scheduler.DefaultScheduler">
     <property name="properties">
diff --git 
a/archiva-modules/archiva-web/archiva-webdav/src/test/java/org/apache/archiva/webdav/AbstractRepositoryServletTestCase.java
 
b/archiva-modules/archiva-web/archiva-webdav/src/test/java/org/apache/archiva/webdav/AbstractRepositoryServletTestCase.java
index 56b8445..e81e069 100644
--- 
a/archiva-modules/archiva-web/archiva-webdav/src/test/java/org/apache/archiva/webdav/AbstractRepositoryServletTestCase.java
+++ 
b/archiva-modules/archiva-web/archiva-webdav/src/test/java/org/apache/archiva/webdav/AbstractRepositoryServletTestCase.java
@@ -30,6 +30,7 @@ import org.apache.archiva.indexer.ArchivaIndexingContext;
 import org.apache.archiva.repository.base.ArchivaRepositoryRegistry;
 import org.apache.archiva.repository.ManagedRepository;
 import org.apache.archiva.repository.RepositoryType;
+import org.apache.archiva.repository.base.RepositoryGroupHandler;
 import org.apache.archiva.test.utils.ArchivaSpringJUnit4ClassRunner;
 import org.apache.archiva.webdav.httpunit.MkColMethodWebRequest;
 import org.apache.commons.io.FileUtils;
@@ -99,6 +100,9 @@ public abstract class AbstractRepositoryServletTestCase
     @Inject
     protected ApplicationContext applicationContext;
 
+    @SuppressWarnings( "unused" )
+    @Inject
+    RepositoryGroupHandler repositoryGroupHandler;
 
     @Inject
     ArchivaRepositoryRegistry repositoryRegistry;
diff --git 
a/archiva-modules/archiva-web/archiva-webdav/src/test/java/org/apache/archiva/webdav/ArchivaDavResourceFactoryTest.java
 
b/archiva-modules/archiva-web/archiva-webdav/src/test/java/org/apache/archiva/webdav/ArchivaDavResourceFactoryTest.java
index 450a3b9..e2b259c 100644
--- 
a/archiva-modules/archiva-web/archiva-webdav/src/test/java/org/apache/archiva/webdav/ArchivaDavResourceFactoryTest.java
+++ 
b/archiva-modules/archiva-web/archiva-webdav/src/test/java/org/apache/archiva/webdav/ArchivaDavResourceFactoryTest.java
@@ -34,6 +34,7 @@ import org.apache.archiva.configuration.FileTypes;
 import org.apache.archiva.configuration.RepositoryGroupConfiguration;
 import org.apache.archiva.metadata.repository.storage.RepositoryPathTranslator;
 import org.apache.archiva.repository.ManagedRepositoryContent;
+import org.apache.archiva.repository.base.RepositoryGroupHandler;
 import org.apache.archiva.repository.maven.content.MavenContentHelper;
 import 
org.apache.archiva.repository.maven.metadata.storage.ArtifactMappingProvider;
 import org.apache.archiva.proxy.ProxyRegistry;
@@ -158,6 +159,10 @@ public class ArchivaDavResourceFactoryTest
     @Inject
     FileTypes fileTypes;
 
+    @SuppressWarnings( "unused" )
+    @Inject
+    RepositoryGroupHandler repositoryGroupHandler;
+
     public Path getProjectBase() {
         if (this.projectBase.get()==null) {
             String pathVal = System.getProperty("mvn.project.base.dir");
@@ -194,8 +199,9 @@ public class ArchivaDavResourceFactoryTest
         expect (archivaConfiguration.getDefaultLocale()).andReturn( 
Locale.getDefault() ).anyTimes();
         archivaConfiguration.addListener( EasyMock.anyObject(  ) );
         expectLastCall().times(0, 4);
-        archivaConfiguration.save( config );
-
+        archivaConfiguration.save( eq(config));
+        expectLastCall().times( 0, 5 );
+        archivaConfiguration.save( eq(config), EasyMock.anyString());
         expectLastCall().times( 0, 5 );
         archivaConfigurationControl.replay();
 
diff --git 
a/archiva-modules/archiva-web/archiva-webdav/src/test/java/org/apache/archiva/webdav/RepositoryServletSecurityTest.java
 
b/archiva-modules/archiva-web/archiva-webdav/src/test/java/org/apache/archiva/webdav/RepositoryServletSecurityTest.java
index 0385c25..b8310c7 100644
--- 
a/archiva-modules/archiva-web/archiva-webdav/src/test/java/org/apache/archiva/webdav/RepositoryServletSecurityTest.java
+++ 
b/archiva-modules/archiva-web/archiva-webdav/src/test/java/org/apache/archiva/webdav/RepositoryServletSecurityTest.java
@@ -35,6 +35,7 @@ import org.apache.archiva.redback.users.User;
 import org.apache.archiva.redback.users.memory.SimpleUser;
 import org.apache.archiva.repository.RepositoryRegistry;
 import org.apache.archiva.metadata.audit.TestAuditListener;
+import org.apache.archiva.repository.base.RepositoryGroupHandler;
 import org.apache.archiva.security.ServletAuthenticator;
 import org.apache.archiva.security.common.ArchivaRoleConstants;
 import org.apache.archiva.test.utils.ArchivaSpringJUnit4ClassRunner;
@@ -91,6 +92,10 @@ public class RepositoryServletSecurityTest
     @Inject
     protected RepositoryRegistry repositoryRegistry;
 
+    @SuppressWarnings( "unused" )
+    @Inject
+    RepositoryGroupHandler repositoryGroupHandler;
+
     private DavSessionProvider davSessionProvider;
 
     private IMocksControl servletAuthControl;

Reply via email to