You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@archiva.apache.org by ma...@apache.org on 2021/07/04 15:36:51 UTC

[archiva] 02/03: Additional tests and refactoring

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

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

commit 11d469489e533a0e038b0a44e732595efcd082ed
Author: Martin Stockhammer <ma...@apache.org>
AuthorDate: Sun Jul 4 17:10:10 2021 +0200

    Additional tests and refactoring
---
 .../archiva/configuration/Configuration.java       |  10 +-
 .../archiva/repository/RepositoryHandler.java      |  52 +++--
 .../repository/features/AbstractFeature.java       |   5 +-
 .../repository/features/IndexCreationFeature.java  |   2 +-
 .../archiva/repository/validation/ErrorKeys.java   |   4 +-
 .../repository/validation/RepositoryValidator.java |   2 +
 .../repository/base/AbstractRepository.java        |   8 -
 .../repository/base/AbstractRepositoryHandler.java |  49 +++++
 .../base/group/BasicRepositoryGroup.java           | 181 ++++++++++++++++
 .../base/group/BasicRepositoryGroupValidator.java  |  26 ++-
 .../base/group/RepositoryGroupHandler.java         |  47 ++---
 .../managed/BasicManagedRepositoryValidator.java   |   7 +-
 .../base/managed/ManagedRepositoryHandler.java     |  87 +++++++-
 .../group/BasicRepositoryGroupValidatorTest.java   | 201 ++++++++++++++++++
 ...orTest.java => RepositoryGroupHandlerTest.java} |  97 ++++++++-
 .../BasicManagedRepositoryValidatorTest.java       | 228 +++++++++++++++++++++
 .../archiva/rest/api/model/v2/RepositoryGroup.java |  27 ++-
 .../services/v2/DefaultRepositoryGroupService.java |  83 +++++---
 .../v2/NativeRepositoryGroupServiceTest.java       |  25 ++-
 19 files changed, 1002 insertions(+), 139 deletions(-)

diff --git a/archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/archiva/configuration/Configuration.java b/archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/archiva/configuration/Configuration.java
index 4d62e81..1bd3bce 100644
--- a/archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/archiva/configuration/Configuration.java
+++ b/archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/archiva/configuration/Configuration.java
@@ -695,15 +695,9 @@ public class Configuration
     
     public RepositoryGroupConfiguration findRepositoryGroupById( String id )
     {
-        if ( repositoryGroups != null )
+        if ( repositoryGroups != null && id!=null)
         {
-            for ( RepositoryGroupConfiguration group : (java.util.List<RepositoryGroupConfiguration>) repositoryGroups )
-            {
-                if ( group.getId().equals( id ) )
-                {
-                    return group;
-                }
-            }
+            return ( (java.util.List<RepositoryGroupConfiguration>) repositoryGroups ).stream( ).filter( g -> g != null && id.equals( g.getId( ) ) ).findFirst( ).orElse( null );
         }
         return null;
     }
diff --git a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/RepositoryHandler.java b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/RepositoryHandler.java
index 5dbcfe5..5ff5263 100644
--- a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/RepositoryHandler.java
+++ b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/RepositoryHandler.java
@@ -21,14 +21,16 @@ import org.apache.archiva.configuration.Configuration;
 import org.apache.archiva.repository.validation.CheckedResult;
 import org.apache.archiva.repository.validation.RepositoryChecker;
 import org.apache.archiva.repository.validation.RepositoryValidator;
+import org.apache.archiva.repository.validation.ValidationError;
 import org.apache.archiva.repository.validation.ValidationResponse;
 
 import java.util.Collection;
+import java.util.List;
 import java.util.Map;
 
 /**
- *
- * This is the generic interface that handles different repository flavours.
+ * This is the generic interface that handles different repository flavours, currently for
+ * ManagedRepository, RemoteRepository and RepositoryGroup
  *
  * @author Martin Stockhammer <ma...@apache.org>
  */
@@ -36,21 +38,32 @@ public interface RepositoryHandler<R extends Repository, C>
 {
 
     /**
-     * Creates instances from the archiva configuration. The instances are not registered in the registry.
+     * Initializes the current state from the configuration
+     */
+    void initializeFromConfig( );
+
+    /**
+     * Initializes the repository. E.g. starts scheduling and activate additional processes.
+     * @param repository the repository to initialize
+     */
+    void initialize( R repository );
+
+    /**
+     * Creates new instances from the archiva configuration. The instances are not registered in the registry.
      *
      * @return A map of (repository id, Repository) pairs
      */
-    Map<String, R> newInstancesFromConfig();
+    Map<String, R> newInstancesFromConfig( );
 
     /**
      * Creates a new instance without registering and without updating the archiva configuration
      *
      * @param type the repository type
-     * @param id the repository identifier
+     * @param id   the repository identifier
      * @return the repository instance
      * @throws RepositoryException if the creation failed
      */
-    R newInstance(RepositoryType type, String id) throws RepositoryException;
+    R newInstance( RepositoryType type, String id ) throws RepositoryException;
 
     /**
      * Creates a new instance and updates the given configuration object.
@@ -90,7 +103,7 @@ public interface RepositoryHandler<R extends Repository, C>
      * and not initialized. References are not updated.
      *
      * @param repositoryConfiguration the repository configuration
-     * @param configuration the configuration instance
+     * @param configuration           the configuration instance
      * @return the repository instance that was created or updated
      * @throws RepositoryException if the update or creation failed
      */
@@ -102,12 +115,12 @@ public interface RepositoryHandler<R extends Repository, C>
      * If the checker returns a valid result, the registry is updated and configuration is saved.
      *
      * @param repositoryConfiguration the repository configuration
-     * @param checker the checker that validates the repository data
+     * @param checker                 the checker that validates the repository data
      * @return the repository and the check result
      * @throws RepositoryException if the creation or update failed
      */
     <D> CheckedResult<R, D>
-    putWithCheck( C repositoryConfiguration, RepositoryChecker<R, D> checker) throws RepositoryException;
+    putWithCheck( C repositoryConfiguration, RepositoryChecker<R, D> checker ) throws RepositoryException;
 
     /**
      * Removes the given repository from the registry and updates references and saves the new configuration.
@@ -121,7 +134,7 @@ public interface RepositoryHandler<R extends Repository, C>
      * Removes the given repository from the registry and updates only the given configuration instance.
      * The archiva registry is not updated
      *
-     * @param id the repository identifier
+     * @param id            the repository identifier
      * @param configuration the configuration to update
      * @throws RepositoryException if the repository could not be removed
      */
@@ -141,7 +154,7 @@ public interface RepositoryHandler<R extends Repository, C>
      * @param repo the repository that should be cloned
      * @return a newly created instance with the same repository data
      */
-    R clone(R repo) throws RepositoryException;
+    R clone( R repo ) throws RepositoryException;
 
     /**
      * Updates the references and stores updates in the given <code>configuration</code> instance.
@@ -149,7 +162,7 @@ public interface RepositoryHandler<R extends Repository, C>
      * This method may register/unregister repositories depending on the implementation. That means there is no simple
      * way to roll back, if an error occurs.
      *
-     * @param repo the repository for which references are updated
+     * @param repo                    the repository for which references are updated
      * @param repositoryConfiguration the repository configuration
      */
     void updateReferences( R repo, C repositoryConfiguration ) throws RepositoryException;
@@ -159,10 +172,11 @@ public interface RepositoryHandler<R extends Repository, C>
      *
      * @return the list of repositories
      */
-    Collection<R> getAll();
+    Collection<R> getAll( );
 
     /**
      * Returns a validator that can be used to validate repository data
+     *
      * @return a validator instance
      */
     RepositoryValidator<R> getValidator( );
@@ -175,7 +189,7 @@ public interface RepositoryHandler<R extends Repository, C>
      * @param repository the repository to validate against
      * @return the result of the validation.
      */
-    ValidationResponse<R> validateRepository( R repository);
+    CheckedResult<R,Map<String, List<ValidationError>>> validateRepository( R repository );
 
     /**
      * Validates the set attributes of the given repository instance for a repository update and returns the validation result.
@@ -185,25 +199,25 @@ public interface RepositoryHandler<R extends Repository, C>
      * @param repository the repository to validate against
      * @return the result of the validation.
      */
-    ValidationResponse<R> validateRepositoryForUpdate( R repository);
-
+    CheckedResult<R,Map<String, List<ValidationError>>> validateRepositoryForUpdate( R repository );
 
 
     /**
      * Returns <code>true</code>, if the repository is registered with the given id, otherwise <code>false</code>
+     *
      * @param id the repository identifier
      * @return <code>true</code>, if it is registered, otherwise <code>false</code>
      */
-    boolean has(String id);
+    boolean has( String id );
 
     /**
      * Initializes
      */
-    void init();
+    void init( );
 
     /**
      * Closes the handler
      */
-    void close();
+    void close( );
 
 }
diff --git a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/features/AbstractFeature.java b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/features/AbstractFeature.java
index d74d0eb..2aeeb3b 100644
--- a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/features/AbstractFeature.java
+++ b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/features/AbstractFeature.java
@@ -34,7 +34,10 @@ public class AbstractFeature {
     }
 
     AbstractFeature(EventHandler listener) {
-        this.listener.add(listener);
+        if (listener!=null)
+        {
+            this.listener.add( listener );
+        }
     }
 
     AbstractFeature(Collection<EventHandler> listeners) {
diff --git a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/features/IndexCreationFeature.java b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/features/IndexCreationFeature.java
index fafd276..7f5874e 100644
--- a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/features/IndexCreationFeature.java
+++ b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/features/IndexCreationFeature.java
@@ -173,7 +173,7 @@ public class IndexCreationFeature extends AbstractFeature implements RepositoryF
     /**
      * Sets the path (relative or absolute) of the packed index.
      *
-     * Throws a {@link RepositoryIndexEvent.Index#PACKED_INDEX_URI_CHANGE}, if the value changes.
+     * Throws a {@link RepositoryIndexEvent#packedIndexUriChange(Object, Repository, URI, URI)}, if the value changes.
      *
      * @param packedIndexPath the new path uri for the packed index
      */
diff --git a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/validation/ErrorKeys.java b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/validation/ErrorKeys.java
index 315d4df..41808c2 100644
--- a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/validation/ErrorKeys.java
+++ b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/validation/ErrorKeys.java
@@ -33,6 +33,6 @@ public interface ErrorKeys
     String MAX_LENGTH_EXCEEDED = "max_length";
     String INVALID_CHARS = "invalid_chars";
     String BELOW_MIN = "min";
-    String INVALID_SCHEDULING_EXPRESSION = "scheduling_exp_invalid";
-    String INVALID_LOCATION = "location_invalid";
+    String INVALID_SCHEDULING_EXPRESSION = "invalid_scheduling_exp";
+    String INVALID_LOCATION = "invalid_location";
 }
diff --git a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/validation/RepositoryValidator.java b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/validation/RepositoryValidator.java
index 25382b3..c411f64 100644
--- a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/validation/RepositoryValidator.java
+++ b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/validation/RepositoryValidator.java
@@ -34,7 +34,9 @@ public interface RepositoryValidator<R extends Repository> extends RepositoryChe
 {
 
     String REPOSITORY_ID_VALID_EXPRESSION = "^[a-zA-Z0-9._-]+$";
+    String[] REPOSITORY_ID_ALLOWED = new String[]{"alphanumeric, '.', '-','_'"};
     String REPOSITORY_NAME_VALID_EXPRESSION = "^([a-zA-Z0-9.)/_(-]|\\s)+$";
+    String[] REPOSITORY_NAME_ALLOWED = new String[]{"alphanumeric", "whitespace", "/", "(", ")", "_", ".", "-"};
     String REPOSITORY_LOCATION_VALID_EXPRESSION = "^[-a-zA-Z0-9._/~:?!&amp;=\\\\]+$";
 
 
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/AbstractRepository.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/AbstractRepository.java
index 8430450..c92e227 100644
--- a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/AbstractRepository.java
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/AbstractRepository.java
@@ -287,11 +287,6 @@ public abstract class AbstractRepository implements EditableRepository, EventHan
 
     @Override
     public void setSchedulingDefinition(String cronExpression) {
-        if (StringUtils.isNotEmpty( cronExpression ))
-        {
-            CronParser parser = new CronParser( CRON_DEFINITION );
-            parser.parse( cronExpression ).validate( );
-        }
         this.schedulingDefinition = cronExpression;
     }
 
@@ -302,9 +297,6 @@ public abstract class AbstractRepository implements EditableRepository, EventHan
 
     @Override
     public void setIndexingContext(ArchivaIndexingContext context) {
-        if (this.indexingContext!=null) {
-
-        }
         this.indexingContext = context;
     }
 
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/AbstractRepositoryHandler.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/AbstractRepositoryHandler.java
new file mode 100644
index 0000000..4637687
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/AbstractRepositoryHandler.java
@@ -0,0 +1,49 @@
+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.repository.Repository;
+import org.apache.archiva.repository.RepositoryHandler;
+import org.apache.archiva.repository.validation.CombinedValidator;
+import org.apache.archiva.repository.validation.RepositoryValidator;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Base abstract class for repository handlers.
+ * @author Martin Stockhammer <ma...@apache.org>
+ */
+public abstract class AbstractRepositoryHandler<R extends Repository, C> implements RepositoryHandler<R, C>
+{
+    protected List<RepositoryValidator<R>> initValidators( Class<R> clazz, List<RepositoryValidator<? extends Repository>> repositoryGroupValidatorList) {
+        if (repositoryGroupValidatorList!=null && repositoryGroupValidatorList.size()>0) {
+            return repositoryGroupValidatorList.stream( ).filter(
+                v -> v.isFlavour( clazz )
+            ).map( v -> v.narrowTo( clazz ) ).collect( Collectors.toList( ) );
+        } else {
+            return Collections.emptyList( );
+        }
+    }
+
+    protected CombinedValidator<R> getCombinedValidatdor(Class<R> clazz, List<RepositoryValidator<? extends Repository>> repositoryGroupValidatorList) {
+        return new CombinedValidator<>( clazz, initValidators( clazz, repositoryGroupValidatorList ) );
+    }
+
+}
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/group/BasicRepositoryGroup.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/group/BasicRepositoryGroup.java
new file mode 100644
index 0000000..7f3df85
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/group/BasicRepositoryGroup.java
@@ -0,0 +1,181 @@
+package org.apache.archiva.repository.base.group;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.archiva.common.filelock.DefaultFileLockManager;
+import org.apache.archiva.common.filelock.FileLockManager;
+import org.apache.archiva.repository.EditableRepositoryGroup;
+import org.apache.archiva.repository.ManagedRepository;
+import org.apache.archiva.repository.ReleaseScheme;
+import org.apache.archiva.repository.RepositoryCapabilities;
+import org.apache.archiva.repository.RepositoryGroup;
+import org.apache.archiva.repository.RepositoryType;
+import org.apache.archiva.repository.StandardCapabilities;
+import org.apache.archiva.repository.base.AbstractRepository;
+import org.apache.archiva.repository.base.managed.BasicManagedRepository;
+import org.apache.archiva.repository.features.IndexCreationFeature;
+import org.apache.archiva.repository.storage.RepositoryStorage;
+import org.apache.archiva.repository.storage.fs.FilesystemStorage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Martin Stockhammer <ma...@apache.org>
+ */
+public class BasicRepositoryGroup extends AbstractRepository implements EditableRepositoryGroup
+{
+    private static final RepositoryCapabilities CAPABILITIES = new StandardCapabilities(
+        new ReleaseScheme[] { ReleaseScheme.RELEASE, ReleaseScheme.SNAPSHOT },
+        new String[] {},
+        new String[] {},
+        new String[] {IndexCreationFeature.class.getName()},
+        false,
+        false,
+        false,
+        false,
+        false
+    );
+
+    private int mergedIndexTtl  = 0;
+    private boolean hasIndex = false;
+
+    private final Logger log = LoggerFactory.getLogger(BasicRepositoryGroup.class);
+
+    private List<ManagedRepository> repositories = new ArrayList<>(  );
+
+    public BasicRepositoryGroup( String id, String name, RepositoryStorage repositoryStorage )
+    {
+        super( RepositoryType.MAVEN, id, name, repositoryStorage );
+        IndexCreationFeature feature = new IndexCreationFeature( this, null );
+        feature.setLocalIndexPath( repositoryStorage.getRoot( ).resolve(".indexer") );
+        feature.setLocalPackedIndexPath( repositoryStorage.getRoot( ).resolve(".index") );
+        addFeature( feature );
+    }
+
+    @Override
+    public List<ManagedRepository> getRepositories( )
+    {
+        return repositories;
+    }
+
+    @Override
+    public boolean contains( ManagedRepository repository )
+    {
+        return repositories.contains( repository );
+    }
+
+    @Override
+    public boolean contains( String id )
+    {
+        return repositories.stream( ).anyMatch( v -> id.equals( v.getId( ) ) );
+    }
+
+    @Override
+    public int getMergedIndexTTL( )
+    {
+        return mergedIndexTtl;
+    }
+
+    @Override
+    public boolean hasIndex( )
+    {
+        return hasIndex;
+    }
+
+    @Override
+    public RepositoryCapabilities getCapabilities( )
+    {
+        return CAPABILITIES;
+    }
+
+    @Override
+    public void clearRepositories( )
+    {
+        this.repositories.clear( );
+    }
+
+    @Override
+    public void setRepositories( List<ManagedRepository> repositories )
+    {
+        this.repositories.clear();
+        this.repositories.addAll( repositories );
+    }
+
+    @Override
+    public void addRepository( ManagedRepository repository )
+    {
+        if ( !this.repositories.contains( repository ) )
+        {
+            this.repositories.add( repository );
+        }
+    }
+
+    @Override
+    public void addRepository( int index, ManagedRepository repository )
+    {
+        if (!this.repositories.contains( repository )) {
+            this.repositories.add( index, repository );
+        }
+    }
+
+    @Override
+    public boolean removeRepository( ManagedRepository repository )
+    {
+        return this.repositories.remove( repository );
+    }
+
+    @Override
+    public ManagedRepository removeRepository( String repoId )
+    {
+        for (ManagedRepository repo : this.repositories) {
+
+            if (repoId.equals( repo.getId() )) {
+                this.repositories.remove( repo );
+                return repo;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void setMergedIndexTTL( int timeInSeconds )
+    {
+        this.mergedIndexTtl = timeInSeconds;
+    }
+
+    /**
+     * Creates a filesystem based repository instance. The path is built by basePath/repository-id
+     *
+     * @param id The repository id
+     * @param name The name of the repository
+     * @param repositoryPath The path to the repository
+     * @return The repository instance
+     * @throws IOException
+     */
+    public static BasicRepositoryGroup newFilesystemInstance( String id, String name, Path repositoryPath) throws IOException {
+        FileLockManager lockManager = new DefaultFileLockManager();
+        FilesystemStorage storage = new FilesystemStorage(repositoryPath, lockManager);
+        return new BasicRepositoryGroup(id, name, storage);
+    }
+
+}
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/group/BasicRepositoryGroupValidator.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/group/BasicRepositoryGroupValidator.java
index 308dd3a..209499f 100644
--- a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/group/BasicRepositoryGroupValidator.java
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/group/BasicRepositoryGroupValidator.java
@@ -45,7 +45,9 @@ public class BasicRepositoryGroupValidator extends AbstractRepositoryValidator<R
 {
 
     private static final String CATEGORY = "repository_group";
-    private static final Pattern REPO_GROUP_ID_PATTERN = Pattern.compile( "[A-Za-z0-9._\\-]+" );
+    private static final Pattern REPOSITORY_ID_VALID_EXPRESSION_PATTERN = Pattern.compile( REPOSITORY_ID_VALID_EXPRESSION );
+    private static final Pattern REPOSITORY_NAME_VALID_EXPRESSION_PATTERN = Pattern.compile( REPOSITORY_NAME_VALID_EXPRESSION );
+
     private final ConfigurationHandler configurationHandler;
 
     private RepositoryRegistry repositoryRegistry;
@@ -60,8 +62,12 @@ public class BasicRepositoryGroupValidator extends AbstractRepositoryValidator<R
     @Override
     public ValidationResponse<RepositoryGroup> apply( RepositoryGroup repositoryGroup, boolean updateMode ) throws IllegalArgumentException
     {
-        final String repoGroupId = repositoryGroup.getId( );
         Map<String, List<ValidationError>> errors = null;
+        if (repositoryGroup==null) {
+            errors = appendError( null, "object", ISNULL );
+            return new ValidationResponse<>( repositoryGroup, errors );
+        }
+        final String repoGroupId = repositoryGroup.getId( );
         if ( StringUtils.isBlank( repoGroupId ) )
         {
             errors = appendError( null, "id", ISEMPTY );
@@ -73,10 +79,10 @@ public class BasicRepositoryGroupValidator extends AbstractRepositoryValidator<R
 
         }
 
-        Matcher matcher = REPO_GROUP_ID_PATTERN.matcher( repoGroupId );
+        Matcher matcher = REPOSITORY_ID_VALID_EXPRESSION_PATTERN.matcher( repoGroupId );
         if ( !matcher.matches( ) )
         {
-            errors = appendError( errors, "id", INVALID_CHARS, repoGroupId, new String[]{"alphanumeric, '.', '-','_'"} );
+            errors = appendError( errors, "id", INVALID_CHARS, repoGroupId, REPOSITORY_ID_ALLOWED );
         }
 
         if ( repositoryGroup.getMergedIndexTTL( ) <= 0 )
@@ -84,6 +90,18 @@ public class BasicRepositoryGroupValidator extends AbstractRepositoryValidator<R
             errors = appendError( errors, "merged_index_ttl",BELOW_MIN, "0" );
         }
 
+        if (StringUtils.isBlank( repositoryGroup.getName() )) {
+            errors = appendError( errors, "name", ISEMPTY );
+        } else
+        {
+            matcher = REPOSITORY_NAME_VALID_EXPRESSION_PATTERN.matcher( repositoryGroup.getName( ) );
+            if ( !matcher.matches( ) )
+            {
+                errors = appendError( errors, "name", INVALID_CHARS, repoGroupId, REPOSITORY_NAME_ALLOWED );
+            }
+        }
+
+
 
         if ( repositoryRegistry != null && !updateMode )
         {
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/group/RepositoryGroupHandler.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/group/RepositoryGroupHandler.java
index dba60f8..f77b745 100644
--- a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/group/RepositoryGroupHandler.java
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/group/RepositoryGroupHandler.java
@@ -22,6 +22,7 @@ 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.base.AbstractRepositoryHandler;
 import org.apache.archiva.repository.base.ArchivaRepositoryRegistry;
 import org.apache.archiva.repository.base.ConfigurationHandler;
 import org.apache.archiva.repository.validation.CheckedResult;
@@ -37,9 +38,9 @@ import org.apache.archiva.repository.RepositoryType;
 import org.apache.archiva.repository.event.RepositoryEvent;
 import org.apache.archiva.repository.features.IndexCreationFeature;
 import org.apache.archiva.repository.storage.StorageAsset;
-import org.apache.archiva.repository.validation.CombinedValidator;
 import org.apache.archiva.repository.validation.RepositoryChecker;
 import org.apache.archiva.repository.validation.RepositoryValidator;
+import org.apache.archiva.repository.validation.ValidationError;
 import org.apache.archiva.repository.validation.ValidationResponse;
 import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
@@ -70,7 +71,9 @@ import static org.apache.archiva.indexer.ArchivaIndexManager.DEFAULT_INDEX_PATH;
  * @author Martin Stockhammer <ma...@apache.org>
  */
 @Service( "repositoryGroupHandler#default" )
-public class RepositoryGroupHandler implements RepositoryHandler<RepositoryGroup, RepositoryGroupConfiguration>
+public class RepositoryGroupHandler
+    extends AbstractRepositoryHandler<RepositoryGroup, RepositoryGroupConfiguration>
+    implements RepositoryHandler<RepositoryGroup, RepositoryGroupConfiguration>
 {
     private static final Logger log = LoggerFactory.getLogger( RepositoryGroupHandler.class );
 
@@ -90,28 +93,18 @@ public class RepositoryGroupHandler implements RepositoryHandler<RepositoryGroup
      * @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
+     * @param repositoryValidatorList the list of validators that are registered
      */
     public RepositoryGroupHandler( ArchivaRepositoryRegistry repositoryRegistry,
                                    ConfigurationHandler configurationHandler,
                                    @Named( "mergedRemoteIndexesScheduler#default" ) MergedRemoteIndexesScheduler mergedRemoteIndexesScheduler,
-                                   List<RepositoryValidator<? extends Repository>> repositoryGroupValidatorList
+                                   List<RepositoryValidator<? extends Repository>> repositoryValidatorList
                                    )
     {
         this.configurationHandler = configurationHandler;
         this.mergedRemoteIndexesScheduler = mergedRemoteIndexesScheduler;
         this.repositoryRegistry = repositoryRegistry;
-        List<RepositoryValidator<RepositoryGroup>> validatorList = initValidators( repositoryGroupValidatorList );
-        this.validator = new CombinedValidator<>( RepositoryGroup.class, validatorList );
-    }
-
-    private List<RepositoryValidator<RepositoryGroup>> initValidators(List<RepositoryValidator<? extends Repository>> repositoryGroupValidatorList) {
-        if (repositoryGroupValidatorList!=null && repositoryGroupValidatorList.size()>0) {
-            return repositoryGroupValidatorList.stream( ).filter(
-                v -> v.isFlavour( RepositoryGroup.class )
-            ).map( v -> v.narrowTo( RepositoryGroup.class ) ).collect( Collectors.toList( ) );
-        } else {
-            return Collections.emptyList( );
-        }
+        this.validator = getCombinedValidatdor( RepositoryGroup.class, repositoryValidatorList );
     }
 
     @Override
@@ -124,13 +117,14 @@ public class RepositoryGroupHandler implements RepositoryHandler<RepositoryGroup
         this.repositoryRegistry.registerGroupHandler( this );
     }
 
+    @Override
     public void initializeFromConfig( )
     {
         this.repositoryGroups.clear( );
         this.repositoryGroups.putAll( newInstancesFromConfig( ) );
         for ( RepositoryGroup group : this.repositoryGroups.values( ) )
         {
-            initializeGroup( group );
+            initialize( group );
         }
     }
 
@@ -151,7 +145,8 @@ public class RepositoryGroupHandler implements RepositoryHandler<RepositoryGroup
         this.groupsDirectory = baseDir;
     }
 
-    private void initializeGroup( RepositoryGroup repositoryGroup )
+    @Override
+    public void initialize( RepositoryGroup repositoryGroup )
     {
         StorageAsset indexDirectory = getMergedIndexDirectory( repositoryGroup );
         if ( !indexDirectory.exists( ) )
@@ -295,7 +290,7 @@ public class RepositoryGroupHandler implements RepositoryHandler<RepositoryGroup
                 }
                 configuration.addRepositoryGroup( newCfg );
                 configurationHandler.save( configuration, ConfigurationHandler.REGISTRY_EVENT_TAG );
-                initializeGroup( repositoryGroup );
+                initialize( repositoryGroup );
             }
             finally
             {
@@ -354,7 +349,7 @@ public class RepositoryGroupHandler implements RepositoryHandler<RepositoryGroup
                 }
                 configurationHandler.save( configuration, ConfigurationHandler.REGISTRY_EVENT_TAG );
                 updateReferences( currentRepository, repositoryGroupConfiguration );
-                initializeGroup( currentRepository );
+                initialize( currentRepository );
                 this.repositoryGroups.put( id, currentRepository );
             }
             catch ( IndeterminateConfigurationException | RegistryException | RepositoryException e )
@@ -374,7 +369,7 @@ public class RepositoryGroupHandler implements RepositoryHandler<RepositoryGroup
                         log.error( "Fatal error, config save during rollback failed: {}", e.getMessage( ), e );
                     }
                     updateReferences( oldRepository, oldCfg  );
-                    initializeGroup( oldRepository );
+                    initialize( oldRepository );
                 }
                 log.error( "Could not save the configuration for repository group {}: {}", id, e.getMessage( ), e );
                 if (e instanceof RepositoryException) {
@@ -559,7 +554,7 @@ public class RepositoryGroupHandler implements RepositoryHandler<RepositoryGroup
     @Override
     public void updateReferences( RepositoryGroup repo, RepositoryGroupConfiguration repositoryConfiguration ) throws RepositoryException
     {
-        if ( repo instanceof EditableRepositoryGroup )
+        if ( repo instanceof EditableRepositoryGroup && repositoryConfiguration!=null)
         {
             EditableRepositoryGroup eGroup = (EditableRepositoryGroup) repo;
             eGroup.setRepositories( repositoryConfiguration.getRepositories( ).stream( )
@@ -581,17 +576,17 @@ public class RepositoryGroupHandler implements RepositoryHandler<RepositoryGroup
     }
 
     @Override
-    public ValidationResponse<RepositoryGroup> validateRepository( RepositoryGroup repository )
+    public CheckedResult<RepositoryGroup, Map<String, List<ValidationError>>> validateRepository( RepositoryGroup repository )
     {
-        return null;
+        return this.validator.apply( repository );
+
     }
 
     @Override
-    public ValidationResponse<RepositoryGroup> validateRepositoryForUpdate( RepositoryGroup repository )
+    public CheckedResult<RepositoryGroup,Map<String, List<ValidationError>>> validateRepositoryForUpdate( RepositoryGroup repository )
     {
-        return null;
+        return this.validator.applyForUpdate( repository );
     }
-
     @Override
     public boolean has( String id )
     {
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/managed/BasicManagedRepositoryValidator.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/managed/BasicManagedRepositoryValidator.java
index 5b9e3a7..745ed9d 100644
--- a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/managed/BasicManagedRepositoryValidator.java
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/managed/BasicManagedRepositoryValidator.java
@@ -64,7 +64,8 @@ public class BasicManagedRepositoryValidator extends AbstractRepositoryValidator
     {
         Map<String, List<ValidationError>> errors = null;
         if (managedRepository==null) {
-            errors = appendError( errors, "id", ISNULL );
+            errors = appendError( errors, "object", ISNULL );
+            return new ValidationResponse<>( managedRepository, errors );
         }
         final String repoId = managedRepository.getId( );
         if ( StringUtils.isBlank( repoId ) ) {
@@ -89,7 +90,7 @@ public class BasicManagedRepositoryValidator extends AbstractRepositoryValidator
 
         if ( !REPOSITORY_ID_VALID_EXPRESSION_PATTERN.matcher( repoId ).matches( ) )
         {
-            errors = appendError( errors, "id", INVALID_CHARS, repoId, new String[]{"alphanumeric", "_", ".", "-"} );
+            errors = appendError( errors, "id", INVALID_CHARS, repoId, REPOSITORY_ID_ALLOWED );
         }
         if ( StringUtils.isBlank( managedRepository.getName() ) )
         {
@@ -98,7 +99,7 @@ public class BasicManagedRepositoryValidator extends AbstractRepositoryValidator
 
         if ( !REPOSITORY_NAME_VALID_EXPRESSION_PATTERN.matcher( managedRepository.getName() ).matches( ) )
         {
-            errors = appendError( errors, "name", INVALID_CHARS, managedRepository.getName( ), new String[]{"alphanumeric", "whitespace", "/", "(", ")", "_", ".", "-"} );
+            errors = appendError( errors, "name", INVALID_CHARS, managedRepository.getName( ), REPOSITORY_NAME_ALLOWED );
         }
 
         String cronExpression = managedRepository.getSchedulingDefinition( );
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/managed/ManagedRepositoryHandler.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/managed/ManagedRepositoryHandler.java
index 3aa65de..c7b10c6 100644
--- a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/managed/ManagedRepositoryHandler.java
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/managed/ManagedRepositoryHandler.java
@@ -19,20 +19,30 @@ package org.apache.archiva.repository.base.managed;
 
 import org.apache.archiva.configuration.Configuration;
 import org.apache.archiva.configuration.ManagedRepositoryConfiguration;
-import org.apache.archiva.repository.base.ArchivaRepositoryRegistry;
-import org.apache.archiva.repository.base.ConfigurationHandler;
-import org.apache.archiva.repository.validation.CheckedResult;
 import org.apache.archiva.repository.ManagedRepository;
+import org.apache.archiva.repository.Repository;
 import org.apache.archiva.repository.RepositoryException;
 import org.apache.archiva.repository.RepositoryHandler;
 import org.apache.archiva.repository.RepositoryType;
+import org.apache.archiva.repository.base.AbstractRepositoryHandler;
+import org.apache.archiva.repository.base.ArchivaRepositoryRegistry;
+import org.apache.archiva.repository.base.ConfigurationHandler;
+import org.apache.archiva.repository.features.StagingRepositoryFeature;
+import org.apache.archiva.repository.validation.CheckedResult;
 import org.apache.archiva.repository.validation.RepositoryChecker;
 import org.apache.archiva.repository.validation.RepositoryValidator;
 import org.apache.archiva.repository.validation.ValidationResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-import javax.inject.Named;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * Handler implementation for managed repositories.
@@ -40,19 +50,84 @@ import java.util.Map;
  * @author Martin Stockhammer <ma...@apache.org>
  */
 public class ManagedRepositoryHandler
+    extends AbstractRepositoryHandler<ManagedRepository, ManagedRepositoryConfiguration>
 implements RepositoryHandler<ManagedRepository, ManagedRepositoryConfiguration>
 {
+    private static final Logger log = LoggerFactory.getLogger( ManagedRepositoryHandler.class );
+    private final ConfigurationHandler configurationHandler;
+    private final ArchivaRepositoryRegistry repositoryRegistry;
+    private final RepositoryValidator<ManagedRepository> validator;
+    private Map<String, ManagedRepository> managedRepositories = new HashMap<>(  );
+    private Map<String, ManagedRepository> uManagedRepositories = Collections.unmodifiableMap( managedRepositories );
+
 
     public ManagedRepositoryHandler( ArchivaRepositoryRegistry repositoryRegistry,
                                      ConfigurationHandler configurationHandler,
-                                     @Named( "repositoryValidator#common#managed") RepositoryValidator<ManagedRepository> managedRepositoryValidator )
+                                     List<RepositoryValidator<? extends Repository>> repositoryValidatorList )
+    {
+        this.configurationHandler = configurationHandler;
+        this.repositoryRegistry = repositoryRegistry;
+        this.validator = getCombinedValidatdor( ManagedRepository.class, repositoryValidatorList );
+    }
+
+    @Override
+    public void initializeFromConfig( )
     {
+        this.managedRepositories.clear( );
+        this.managedRepositories.putAll( newInstancesFromConfig( ) );
+        for ( ManagedRepository managedRepository : this.managedRepositories.values( ) )
+        {
+            initialize( managedRepository );
+        }
+
+    }
+
+    @Override
+    public void initialize( ManagedRepository repository )
+    {
+
     }
 
     @Override
     public Map<String, ManagedRepository> newInstancesFromConfig( )
     {
-        return null;
+        try
+        {
+            Set<String> configRepoIds = new HashSet<>( );
+            List<ManagedRepositoryConfiguration> managedRepoConfigs =
+                configurationHandler.getBaseConfiguration( ).getManagedRepositories( );
+
+            if ( managedRepoConfigs == null )
+            {
+                return managedRepositories;
+            }
+
+            for ( ManagedRepositoryConfiguration repoConfig : managedRepoConfigs )
+            {
+                ManagedRepository repo = put( repoConfig, null );
+                configRepoIds.add( repoConfig.getId( ) );
+                if ( repo.supportsFeature( StagingRepositoryFeature.class ) )
+                {
+                    StagingRepositoryFeature stagF = repo.getFeature( StagingRepositoryFeature.class ).get( );
+                    if ( stagF.getStagingRepository( ) != null )
+                    {
+                        configRepoIds.add( stagF.getStagingRepository( ).getId( ) );
+                    }
+                }
+            }
+            List<String> toRemove = managedRepositories.keySet( ).stream( ).filter( id -> !configRepoIds.contains( id ) ).collect( Collectors.toList( ) );
+            for ( String id : toRemove )
+            {
+                ManagedRepository removed = managedRepositories.remove( id );
+                removed.close( );
+            }
+        }
+        catch ( Throwable e )
+        {
+            log.error( "Could not initialize repositories from config: {}", e.getMessage( ), e );
+            return managedRepositories;
+        }
+        return managedRepositories;
     }
 
     @Override
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/base/group/BasicRepositoryGroupValidatorTest.java b/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/base/group/BasicRepositoryGroupValidatorTest.java
new file mode 100644
index 0000000..c049b47
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/base/group/BasicRepositoryGroupValidatorTest.java
@@ -0,0 +1,201 @@
+package org.apache.archiva.repository.base.group;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.archiva.common.filelock.DefaultFileLockManager;
+import org.apache.archiva.common.filelock.FileLockManager;
+import org.apache.archiva.repository.EditableManagedRepository;
+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.RepositoryRegistry;
+import org.apache.archiva.repository.base.ConfigurationHandler;
+import org.apache.archiva.repository.base.managed.BasicManagedRepository;
+import org.apache.archiva.repository.base.managed.BasicManagedRepositoryValidator;
+import org.apache.archiva.repository.mock.ManagedRepositoryContentMock;
+import org.apache.archiva.repository.storage.fs.FilesystemStorage;
+import org.apache.archiva.repository.validation.ValidationResponse;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import javax.inject.Inject;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * @author Martin Stockhammer <ma...@apache.org>
+ */
+@ExtendWith( SpringExtension.class)
+@ContextConfiguration(locations = { "classpath*:/META-INF/spring-context.xml", "classpath:/spring-context.xml" })
+class BasicRepositoryGroupValidatorTest
+{
+
+    @Inject
+    ConfigurationHandler configurationHandler;
+
+    @Inject
+    RepositoryRegistry repositoryRegistry;
+
+    @SuppressWarnings( "unused" )
+    @Inject
+    RepositoryGroupHandler repositoryGroupHandler;
+
+    Path repoBaseDir;
+
+    @AfterEach
+    void cleanup() throws RepositoryException
+    {
+        repositoryRegistry.removeRepository("test");
+    }
+
+    protected EditableManagedRepository createRepository( String id, String name, Path location ) throws IOException
+    {
+        BasicManagedRepository repo = BasicManagedRepository.newFilesystemInstance(id, name, location);
+        repo.setLocation( location.toAbsolutePath().toUri());
+        repo.setContent(new ManagedRepositoryContentMock());
+        return repo;
+    }
+
+    protected EditableRepositoryGroup createGroup(String id, String name) throws IOException
+    {
+        return BasicRepositoryGroup.newFilesystemInstance( id, name, getRepoBaseDir( ).resolve( id ) );
+    }
+
+    private Path getRepoBaseDir() {
+        if (repoBaseDir==null) {
+            try
+            {
+                repoBaseDir = Paths.get(Thread.currentThread( ).getContextClassLoader( ).getResource( "repositories" ).toURI());
+            }
+            catch ( URISyntaxException e )
+            {
+                throw new RuntimeException( "Could not retrieve repository base directory" );
+            }
+        }
+        return repoBaseDir;
+    }
+
+    @Test
+    void apply( ) throws IOException
+    {
+        BasicRepositoryGroupValidator validator = new BasicRepositoryGroupValidator( configurationHandler );
+        EditableRepositoryGroup group = createGroup( "test", "test" );
+        group.setMergedIndexTTL( 360 );
+        ValidationResponse<RepositoryGroup> result = validator.apply( group );
+        assertNotNull( result );
+        assertTrue( result.isValid( ) );
+    }
+
+    @Test
+    void applyWithExisting( ) throws IOException, RepositoryException
+    {
+        BasicRepositoryGroupValidator validator = new BasicRepositoryGroupValidator( configurationHandler );
+        validator.setRepositoryRegistry( repositoryRegistry );
+        EditableRepositoryGroup group = createGroup( "test", "test" );
+        group.setMergedIndexTTL( 360 );
+        repositoryRegistry.putRepositoryGroup( group );
+        EditableRepositoryGroup group2 = createGroup( "test", "test2" );
+        group2.setMergedIndexTTL( 360 );
+        ValidationResponse<RepositoryGroup> result = validator.apply( group2 );
+        assertNotNull( result );
+        assertFalse( result.isValid( ) );
+        assertEquals( "group_exists", result.getResult( ).get( "id" ).get( 0 ).getType( ) );
+    }
+
+    @Test
+    void applyWithBadTTL( ) throws IOException
+    {
+        BasicRepositoryGroupValidator validator = new BasicRepositoryGroupValidator( configurationHandler );
+        EditableRepositoryGroup group = createGroup( "test", "test" );
+        group.setMergedIndexTTL( 0 );
+        ValidationResponse<RepositoryGroup> result = validator.apply( group );
+        assertNotNull( result );
+        assertFalse( result.isValid( ) );
+        assertTrue( result.getResult( ).containsKey( "merged_index_ttl" ) );
+        assertEquals( "repository_group", result.getResult( ).get( "merged_index_ttl" ).get( 0 ).getCategory( ) );
+        assertEquals( "min", result.getResult( ).get( "merged_index_ttl" ).get( 0 ).getType( ) );
+        assertEquals( "merged_index_ttl", result.getResult( ).get( "merged_index_ttl" ).get( 0 ).getAttribute() );
+    }
+
+    @Test
+    void applyWithNullObject( ) throws IOException
+    {
+        BasicRepositoryGroupValidator validator = new BasicRepositoryGroupValidator( configurationHandler );
+        EditableRepositoryGroup group = createGroup( "", "test" );
+        group.setMergedIndexTTL( 0 );
+        ValidationResponse<RepositoryGroup> result = validator.apply( null );
+        assertNotNull( result );
+        assertFalse( result.isValid( ) );
+        assertTrue( result.getResult( ).containsKey( "object" ) );
+        assertEquals( "repository_group", result.getResult( ).get( "object" ).get( 0 ).getCategory( ) );
+        assertEquals( "isnull", result.getResult( ).get( "object" ).get( 0 ).getType( ) );
+        assertEquals( "object", result.getResult( ).get( "object" ).get( 0 ).getAttribute() );
+    }
+
+    @Test
+    void applyWithEmptyId( ) throws IOException
+    {
+        BasicRepositoryGroupValidator validator = new BasicRepositoryGroupValidator( configurationHandler );
+        EditableRepositoryGroup group = createGroup( "", "test" );
+        group.setMergedIndexTTL( 0 );
+        ValidationResponse<RepositoryGroup> result = validator.apply( group );
+        assertNotNull( result );
+        assertFalse( result.isValid( ) );
+        assertTrue( result.getResult( ).containsKey( "id" ) );
+        assertEquals( "repository_group", result.getResult( ).get( "id" ).get( 0 ).getCategory( ) );
+        assertEquals( "empty", result.getResult( ).get( "id" ).get( 0 ).getType( ) );
+        assertEquals( "id", result.getResult( ).get( "id" ).get( 0 ).getAttribute() );
+    }
+
+    @Test
+    void applyWithBadName( ) throws IOException
+    {
+        BasicRepositoryGroupValidator validator = new BasicRepositoryGroupValidator( configurationHandler );
+        validator.setRepositoryRegistry( repositoryRegistry );
+        EditableRepositoryGroup group = createGroup( "test", "badtest\\name" );
+        group.setMergedIndexTTL( 360);
+        ValidationResponse<RepositoryGroup> result = validator.apply( group );
+        assertFalse( result.isValid( ) );
+        assertEquals( 1, result.getResult( ).size( ) );
+        assertEquals( "invalid_chars", result.getResult( ).get( "name" ).get( 0 ).getType( ) );
+    }
+
+    @Test
+    void getFlavour( )
+    {
+        BasicRepositoryGroupValidator validator = new BasicRepositoryGroupValidator( configurationHandler );
+        assertEquals( RepositoryGroup.class, validator.getFlavour( ) );
+    }
+
+    @Test
+    void isFlavour() {
+        BasicRepositoryGroupValidator validator = new BasicRepositoryGroupValidator( configurationHandler );
+        assertTrue( validator.isFlavour( RepositoryGroup.class ) );
+        assertFalse( validator.isFlavour( ManagedRepository.class ) );
+        assertTrue( validator.isFlavour( BasicRepositoryGroup.class ) );
+    }
+}
\ No newline at end of file
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/base/group/BasicManagedRepositoryValidatorTest.java b/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/base/group/RepositoryGroupHandlerTest.java
similarity index 51%
rename from archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/base/group/BasicManagedRepositoryValidatorTest.java
rename to archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/base/group/RepositoryGroupHandlerTest.java
index 5b950f9..26b6fbf 100644
--- a/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/base/group/BasicManagedRepositoryValidatorTest.java
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/base/group/RepositoryGroupHandlerTest.java
@@ -20,29 +20,116 @@ package org.apache.archiva.repository.base.group;
 
 import org.junit.jupiter.api.Test;
 
+import static org.junit.jupiter.api.Assertions.*;
+
 /**
  * @author Martin Stockhammer <ma...@apache.org>
  */
-class BasicManagedRepositoryValidatorTest
+class RepositoryGroupHandlerTest
 {
 
     @Test
-    void apply( )
+    void init( )
+    {
+    }
+
+    @Test
+    void initializeFromConfig( )
+    {
+    }
+
+    @Test
+    void initialize( )
+    {
+    }
+
+    @Test
+    void newInstancesFromConfig( )
+    {
+    }
+
+    @Test
+    void newInstance( )
+    {
+    }
+
+    @Test
+    void testNewInstance( )
+    {
+    }
+
+    @Test
+    void put( )
+    {
+    }
+
+    @Test
+    void testPut( )
+    {
+    }
+
+    @Test
+    void testPut1( )
+    {
+    }
+
+    @Test
+    void putWithCheck( )
+    {
+    }
+
+    @Test
+    void remove( )
+    {
+    }
+
+    @Test
+    void testRemove( )
+    {
+    }
+
+    @Test
+    void get( )
+    {
+    }
+
+    @Test
+    void testClone( )
+    {
+    }
+
+    @Test
+    void updateReferences( )
+    {
+    }
+
+    @Test
+    void getAll( )
+    {
+    }
+
+    @Test
+    void getValidator( )
+    {
+    }
+
+    @Test
+    void validateRepository( )
     {
     }
 
     @Test
-    void applyForUpdate( )
+    void validateRepositoryForUpdate( )
     {
     }
 
     @Test
-    void getFlavour( )
+    void has( )
     {
     }
 
     @Test
-    void isFlavour( )
+    void close( )
     {
     }
 }
\ No newline at end of file
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/base/managed/BasicManagedRepositoryValidatorTest.java b/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/base/managed/BasicManagedRepositoryValidatorTest.java
new file mode 100644
index 0000000..2fe4f67
--- /dev/null
+++ b/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/base/managed/BasicManagedRepositoryValidatorTest.java
@@ -0,0 +1,228 @@
+package org.apache.archiva.repository.base.managed;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.archiva.common.filelock.DefaultFileLockManager;
+import org.apache.archiva.common.filelock.FileLockManager;
+import org.apache.archiva.configuration.ArchivaConfiguration;
+import org.apache.archiva.repository.EditableManagedRepository;
+import org.apache.archiva.repository.ManagedRepository;
+import org.apache.archiva.repository.RepositoryException;
+import org.apache.archiva.repository.RepositoryRegistry;
+import org.apache.archiva.repository.base.ConfigurationHandler;
+import org.apache.archiva.repository.base.group.RepositoryGroupHandler;
+import org.apache.archiva.repository.mock.ManagedRepositoryContentMock;
+import org.apache.archiva.repository.storage.fs.FilesystemStorage;
+import org.apache.archiva.repository.validation.ValidationResponse;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import javax.inject.Inject;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * @author Martin Stockhammer <ma...@apache.org>
+ */
+@ExtendWith( SpringExtension.class)
+@ContextConfiguration(locations = { "classpath*:/META-INF/spring-context.xml", "classpath:/spring-context.xml" })
+class BasicManagedRepositoryValidatorTest
+{
+
+    @Inject
+    ConfigurationHandler configurationHandler;
+
+    @Inject
+    RepositoryRegistry repositoryRegistry;
+
+    @SuppressWarnings( "unused" )
+    @Inject
+    RepositoryGroupHandler repositoryGroupHandler;
+
+    Path repoBaseDir;
+
+    @AfterEach
+    void cleanup() {
+        try
+        {
+            repositoryRegistry.removeRepository( "test" );
+        }
+        catch ( RepositoryException e )
+        {
+            // Ignore this
+        }
+
+    }
+
+    protected EditableManagedRepository createRepository( String id, String name, Path location ) throws IOException
+    {
+        FileLockManager lockManager = new DefaultFileLockManager();
+        FilesystemStorage storage = new FilesystemStorage(location.toAbsolutePath(), lockManager);
+        BasicManagedRepository repo = new BasicManagedRepository(id, name, storage);
+        repo.setLocation( location.toAbsolutePath().toUri());
+        repo.setContent(new ManagedRepositoryContentMock());
+        return repo;
+    }
+
+    private Path getRepoBaseDir() {
+        if (repoBaseDir==null) {
+            try
+            {
+                repoBaseDir = Paths.get(Thread.currentThread( ).getContextClassLoader( ).getResource( "repositories" ).toURI());
+            }
+            catch ( URISyntaxException e )
+            {
+                throw new RuntimeException( "Could not retrieve repository base directory" );
+            }
+        }
+        return repoBaseDir;
+    }
+
+
+    @Test
+    void apply( ) throws IOException
+    {
+        BasicManagedRepositoryValidator validator = new BasicManagedRepositoryValidator( configurationHandler );
+        validator.setRepositoryRegistry( repositoryRegistry );
+        Path repoDir = getRepoBaseDir().resolve("test" );
+        EditableManagedRepository repo = createRepository( "test", "test", repoDir );
+        ValidationResponse<ManagedRepository> result = validator.apply( repo );
+        assertTrue( result.isValid( ) );
+    }
+
+    @Test
+    void applyWithExistingRepo( ) throws IOException, RepositoryException
+    {
+        BasicManagedRepositoryValidator validator = new BasicManagedRepositoryValidator( configurationHandler );
+        validator.setRepositoryRegistry( repositoryRegistry );
+        Path repoDir = getRepoBaseDir().resolve("test" );
+        EditableManagedRepository repo = createRepository( "test", "test", repoDir );
+        Path repoDir2 = getRepoBaseDir().resolve("test2" );
+        EditableManagedRepository repo2 = createRepository( "test", "test", repoDir2 );
+        repositoryRegistry.putRepository( repo );
+        ValidationResponse<ManagedRepository> result = validator.apply( repo );
+        assertFalse( result.isValid( ) );
+        assertEquals( 1, result.getResult( ).size( ) );
+        assertTrue( result.getResult( ).containsKey( "id" ) );
+        assertEquals( "managed_repository", result.getResult( ).get( "id" ).get( 0 ).getCategory( ) );
+        assertEquals( "managed_repo_exists", result.getResult( ).get( "id" ).get( 0 ).getType( ) );
+        assertEquals( "id", result.getResult( ).get( "id" ).get( 0 ).getAttribute() );
+    }
+
+    @Test
+    void applyUpdateWithExistingRepo( ) throws IOException, RepositoryException
+    {
+        BasicManagedRepositoryValidator validator = new BasicManagedRepositoryValidator( configurationHandler );
+        validator.setRepositoryRegistry( repositoryRegistry );
+        Path repoDir = getRepoBaseDir().resolve("test" );
+        EditableManagedRepository repo = createRepository( "test", "test", repoDir );
+        Path repoDir2 = getRepoBaseDir().resolve("test2" );
+        EditableManagedRepository repo2 = createRepository( "test", "test", repoDir2 );
+        repositoryRegistry.putRepository( repo );
+        ValidationResponse<ManagedRepository> result = validator.applyForUpdate( repo );
+        assertTrue( result.isValid( ) );
+        assertEquals( 0, result.getResult( ).size( ) );
+    }
+
+    @Test
+    void applyWithNullObject( ) throws IOException
+    {
+        BasicManagedRepositoryValidator validator = new BasicManagedRepositoryValidator( configurationHandler );
+        validator.setRepositoryRegistry( repositoryRegistry );
+        ValidationResponse<ManagedRepository> result = validator.apply( null );
+        assertFalse( result.isValid( ) );
+        assertEquals( 1, result.getResult( ).size( ) );
+        assertTrue( result.getResult( ).containsKey( "object" ) );
+        assertEquals( "managed_repository", result.getResult( ).get( "object" ).get( 0 ).getCategory( ) );
+        assertEquals( "isnull", result.getResult( ).get( "object" ).get( 0 ).getType( ) );
+        assertEquals( "object", result.getResult( ).get( "object" ).get( 0 ).getAttribute() );
+    }
+
+    @Test
+    void applyWithEmptyId( ) throws IOException
+    {
+        BasicManagedRepositoryValidator validator = new BasicManagedRepositoryValidator( configurationHandler );
+        validator.setRepositoryRegistry( repositoryRegistry );
+        Path repoDir = getRepoBaseDir().resolve("test" );
+        EditableManagedRepository repo = createRepository( "", "test", repoDir );
+        ValidationResponse<ManagedRepository> result = validator.apply( repo );
+        assertFalse( result.isValid( ) );
+        assertEquals( 1, result.getResult( ).size( ) );
+        assertTrue( result.getResult( ).containsKey( "id" ) );
+        assertEquals( "managed_repository", result.getResult( ).get( "id" ).get( 0 ).getCategory( ) );
+        assertEquals( "empty", result.getResult( ).get( "id" ).get( 0 ).getType( ) );
+        assertEquals( "id", result.getResult( ).get( "id" ).get( 0 ).getAttribute() );
+    }
+
+    @Test
+    void applyWithBadName( ) throws IOException
+    {
+        BasicManagedRepositoryValidator validator = new BasicManagedRepositoryValidator( configurationHandler );
+        validator.setRepositoryRegistry( repositoryRegistry );
+        Path repoDir = getRepoBaseDir().resolve("test" );
+        EditableManagedRepository repo = createRepository( "test", "badtest\\name", repoDir );
+        ValidationResponse<ManagedRepository> result = validator.apply( repo );
+        assertFalse( result.isValid( ) );
+        assertEquals( 1, result.getResult( ).size( ) );
+        assertEquals( "invalid_chars", result.getResult( ).get( "name" ).get( 0 ).getType( ) );
+    }
+
+    @Test
+    void applyWithBadSchedulingExpression( ) throws IOException
+    {
+        BasicManagedRepositoryValidator validator = new BasicManagedRepositoryValidator( configurationHandler );
+        validator.setRepositoryRegistry( repositoryRegistry );
+        Path repoDir = getRepoBaseDir().resolve("test" );
+        EditableManagedRepository repo = createRepository( "test", "test", repoDir );
+        repo.setSchedulingDefinition( "xxxxx" );
+        ValidationResponse<ManagedRepository> result = validator.apply( repo );
+        assertFalse( result.isValid( ) );
+        assertEquals( 1, result.getResult( ).size( ) );
+        assertEquals( "invalid_scheduling_exp", result.getResult( ).get( "scheduling_definition" ).get( 0 ).getType( ) );
+    }
+
+    @Test
+    void applyForUpdate( )
+    {
+    }
+
+    @Test
+    void getFlavour( )
+    {
+        BasicManagedRepositoryValidator validator = new BasicManagedRepositoryValidator( configurationHandler );
+        validator.setRepositoryRegistry( repositoryRegistry );
+        assertEquals( ManagedRepository.class, validator.getFlavour( ) );
+    }
+
+    @Test
+    void isFlavour( )
+    {
+        BasicManagedRepositoryValidator validator = new BasicManagedRepositoryValidator( configurationHandler );
+        validator.setRepositoryRegistry( repositoryRegistry );
+        assertTrue( validator.isFlavour( ManagedRepository.class ) );
+        assertTrue( validator.isFlavour( BasicManagedRepository.class ) );
+    }
+}
\ No newline at end of file
diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/RepositoryGroup.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/RepositoryGroup.java
index 1f021d0..1ca68a0 100644
--- a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/RepositoryGroup.java
+++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/RepositoryGroup.java
@@ -55,6 +55,7 @@ public class RepositoryGroup implements Serializable
 {
     private static final long serialVersionUID = -7319687481737616081L;
     private String id;
+    private String name;
     private List<String> repositories = new ArrayList<>(  );
     private String location;
     MergeConfiguration mergeConfiguration;
@@ -72,6 +73,7 @@ public class RepositoryGroup implements Serializable
         MergeConfiguration mergeConfig = new MergeConfiguration( );
         result.setMergeConfiguration( mergeConfig );
         result.setId( modelObj.getId() );
+        result.setName( modelObj.getName() );
         result.setLocation( modelObj.getLocation().toString() );
         result.setRepositories( modelObj.getRepositories().stream().map( Repository::getId ).collect( Collectors.toList()) );
         if (modelObj.supportsFeature( IndexCreationFeature.class )) {
@@ -133,6 +135,17 @@ public class RepositoryGroup implements Serializable
         this.location = location;
     }
 
+    @Schema(description = "The name of the repository group")
+    public String getName( )
+    {
+        return name;
+    }
+
+    public void setName( String name )
+    {
+        this.name = name;
+    }
+
     @Override
     public boolean equals( Object o )
     {
@@ -141,29 +154,21 @@ public class RepositoryGroup implements Serializable
 
         RepositoryGroup that = (RepositoryGroup) o;
 
-        if ( !Objects.equals( id, that.id ) ) return false;
-        if ( !repositories.equals( that.repositories ) )
-            return false;
-        if ( !Objects.equals( location, that.location ) ) return false;
-        return Objects.equals( mergeConfiguration, that.mergeConfiguration );
+        return id.equals( that.id );
     }
 
     @Override
     public int hashCode( )
     {
-        int result = id != null ? id.hashCode( ) : 0;
-        result = 31 * result + repositories.hashCode( );
-        result = 31 * result + ( location != null ? location.hashCode( ) : 0 );
-        result = 31 * result + ( mergeConfiguration != null ? mergeConfiguration.hashCode( ) : 0 );
-        return result;
+        return id.hashCode( );
     }
 
-    @SuppressWarnings( "StringBufferReplaceableByString" )
     @Override
     public String toString( )
     {
         final StringBuilder sb = new StringBuilder( "RepositoryGroup{" );
         sb.append( "id='" ).append( id ).append( '\'' );
+        sb.append( ", name='" ).append( name ).append( '\'' );
         sb.append( ", repositories=" ).append( repositories );
         sb.append( ", location='" ).append( location ).append( '\'' );
         sb.append( ", mergeConfiguration=" ).append( mergeConfiguration );
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 4927b21..9ecd914 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
@@ -37,11 +37,11 @@ package org.apache.archiva.rest.services.v2;/*
 import org.apache.archiva.components.rest.model.PagedResult;
 import org.apache.archiva.components.rest.util.QueryHelper;
 import org.apache.archiva.configuration.RepositoryGroupConfiguration;
-import org.apache.archiva.repository.validation.CheckedResult;
 import org.apache.archiva.repository.EditableRepositoryGroup;
 import org.apache.archiva.repository.RepositoryException;
 import org.apache.archiva.repository.RepositoryRegistry;
 import org.apache.archiva.repository.base.ConfigurationHandler;
+import org.apache.archiva.repository.validation.CheckedResult;
 import org.apache.archiva.repository.validation.ValidationError;
 import org.apache.archiva.rest.api.model.v2.MergeConfiguration;
 import org.apache.archiva.rest.api.model.v2.RepositoryGroup;
@@ -72,7 +72,7 @@ import java.util.stream.Collectors;
  * @see RepositoryGroupService
  * @since 3.0
  */
-@Service("v2.repositoryGroupService#rest")
+@Service( "v2.repositoryGroupService#rest" )
 public class DefaultRepositoryGroupService implements RepositoryGroupService
 {
     private final ConfigurationHandler configurationHandler;
@@ -86,9 +86,9 @@ public class DefaultRepositoryGroupService implements RepositoryGroupService
     final private RepositoryRegistry repositoryRegistry;
 
 
-
     private static final Logger log = LoggerFactory.getLogger( DefaultRepositoryGroupService.class );
     private static final QueryHelper<org.apache.archiva.repository.RepositoryGroup> QUERY_HELPER = new QueryHelper<>( new String[]{"id"} );
+
     static
     {
         QUERY_HELPER.addStringFilter( "id", org.apache.archiva.repository.RepositoryGroup::getId );
@@ -96,7 +96,8 @@ public class DefaultRepositoryGroupService implements RepositoryGroupService
     }
 
 
-    public DefaultRepositoryGroupService( RepositoryRegistry repositoryRegistry, ConfigurationHandler configurationHandler ) {
+    public DefaultRepositoryGroupService( RepositoryRegistry repositoryRegistry, ConfigurationHandler configurationHandler )
+    {
         this.repositoryRegistry = repositoryRegistry;
         this.configurationHandler = configurationHandler;
     }
@@ -137,10 +138,11 @@ public class DefaultRepositoryGroupService implements RepositoryGroupService
     {
         RepositoryGroupConfiguration result = new RepositoryGroupConfiguration( );
         result.setId( group.getId( ) );
+        result.setName( group.getName() );
         result.setLocation( group.getLocation( ) );
         result.setRepositories( group.getRepositories( ) );
         MergeConfiguration mergeConfig = group.getMergeConfiguration( );
-        if (mergeConfig!=null)
+        if ( mergeConfig != null )
         {
             result.setMergedIndexPath( mergeConfig.getMergedIndexPath( ) );
             result.setMergedIndexTtl( mergeConfig.getMergedIndexTtlMinutes( ) );
@@ -153,10 +155,12 @@ public class DefaultRepositoryGroupService implements RepositoryGroupService
     public RepositoryGroup addRepositoryGroup( RepositoryGroup repositoryGroup ) throws ArchivaRestServiceException
     {
         final String groupId = repositoryGroup.getId( );
-        if ( StringUtils.isEmpty( groupId ) ) {
+        if ( StringUtils.isEmpty( groupId ) )
+        {
             throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_INVALID_ID, groupId ), 422 );
         }
-        if (repositoryRegistry.hasRepositoryGroup( groupId )) {
+        if ( repositoryRegistry.hasRepositoryGroup( groupId ) )
+        {
             httpServletResponse.setHeader( "Location", uriInfo.getAbsolutePathBuilder( ).path( groupId ).build( ).toString( ) );
             throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_ID_EXISTS, groupId ), 303 );
         }
@@ -165,13 +169,15 @@ public class DefaultRepositoryGroupService implements RepositoryGroupService
 
             RepositoryGroupConfiguration configuration = toConfig( repositoryGroup );
             CheckedResult<org.apache.archiva.repository.RepositoryGroup, Map<String, List<ValidationError>>> validationResult = repositoryRegistry.putRepositoryGroupAndValidate( configuration );
-                if ( validationResult.isValid( ) )
-                {
-                    httpServletResponse.setStatus( 201 );
-                    return RepositoryGroup.of( validationResult.getRepository() );
-                } else {
-                    throw ValidationException.of( validationResult.getResult() );
-                }
+            if ( validationResult.isValid( ) )
+            {
+                httpServletResponse.setStatus( 201 );
+                return RepositoryGroup.of( validationResult.getRepository( ) );
+            }
+            else
+            {
+                throw ValidationException.of( validationResult.getResult( ) );
+            }
         }
         catch ( RepositoryException e )
         {
@@ -194,19 +200,21 @@ public class DefaultRepositoryGroupService implements RepositoryGroupService
         try
         {
             RepositoryGroupConfiguration configuration = toConfig( repositoryGroup );
-                CheckedResult<org.apache.archiva.repository.RepositoryGroup, Map<String, List<ValidationError>>> validationResult = repositoryRegistry.putRepositoryGroupAndValidate( configuration );
-                if ( validationResult.isValid( ) )
-                {
-                    httpServletResponse.setStatus( 201 );
-                    return RepositoryGroup.of( validationResult.getRepository() );
-                } else {
-                    throw ValidationException.of( validationResult.getResult() );
-                }
+            CheckedResult<org.apache.archiva.repository.RepositoryGroup, Map<String, List<ValidationError>>> validationResult = repositoryRegistry.putRepositoryGroupAndValidate( configuration );
+            if ( validationResult.isValid( ) )
+            {
+                httpServletResponse.setStatus( 201 );
+                return RepositoryGroup.of( validationResult.getRepository( ) );
+            }
+            else
+            {
+                throw ValidationException.of( validationResult.getResult( ) );
+            }
         }
         catch ( RepositoryException e )
         {
             log.error( "Exception during repository group update: {}", e.getMessage( ), e );
-            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_UPDATE_FAILED, e.getMessage() ) );
+            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_UPDATE_FAILED, e.getMessage( ) ) );
 
         }
     }
@@ -221,7 +229,8 @@ public class DefaultRepositoryGroupService implements RepositoryGroupService
         try
         {
             org.apache.archiva.repository.RepositoryGroup group = repositoryRegistry.getRepositoryGroup( repositoryGroupId );
-            if (group==null) {
+            if ( group == null )
+            {
                 throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_NOT_FOUND, "" ), 404 );
             }
             repositoryRegistry.removeRepositoryGroup( group );
@@ -247,21 +256,24 @@ public class DefaultRepositoryGroupService implements RepositoryGroupService
         try
         {
             org.apache.archiva.repository.RepositoryGroup repositoryGroup = repositoryRegistry.getRepositoryGroup( repositoryGroupId );
-            if (repositoryGroup==null) {
+            if ( repositoryGroup == null )
+            {
                 throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_NOT_FOUND, "" ), 404 );
             }
-            if (!(repositoryGroup instanceof EditableRepositoryGroup )) {
+            if ( !( repositoryGroup instanceof EditableRepositoryGroup ) )
+            {
                 log.error( "This group instance is not editable: {}", repositoryGroupId );
                 throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_UPDATE_FAILED, "" ), 500 );
             }
             EditableRepositoryGroup editableRepositoryGroup = (EditableRepositoryGroup) repositoryGroup;
-            if ( editableRepositoryGroup.getRepositories().stream().anyMatch( repo -> repositoryId.equals(repo.getId())) )
+            if ( editableRepositoryGroup.getRepositories( ).stream( ).anyMatch( repo -> repositoryId.equals( repo.getId( ) ) ) )
             {
                 log.info( "Repository {} is already member of group {}", repositoryId, repositoryGroupId );
                 return RepositoryGroup.of( editableRepositoryGroup );
             }
-            org.apache.archiva.repository.ManagedRepository managedRepo = repositoryRegistry.getManagedRepository(repositoryId);
-            if (managedRepo==null) {
+            org.apache.archiva.repository.ManagedRepository managedRepo = repositoryRegistry.getManagedRepository( repositoryId );
+            if ( managedRepo == null )
+            {
                 throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_NOT_FOUND, "" ), 404 );
             }
             editableRepositoryGroup.addRepository( managedRepo );
@@ -270,7 +282,7 @@ public class DefaultRepositoryGroupService implements RepositoryGroupService
         }
         catch ( RepositoryException e )
         {
-            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_UPDATE_FAILED, e.getMessage() ), 500 );
+            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_UPDATE_FAILED, e.getMessage( ) ), 500 );
         }
     }
 
@@ -288,13 +300,16 @@ public class DefaultRepositoryGroupService implements RepositoryGroupService
         try
         {
             org.apache.archiva.repository.RepositoryGroup repositoryGroup = repositoryRegistry.getRepositoryGroup( repositoryGroupId );
-            if (repositoryGroup==null) {
+            if ( repositoryGroup == null )
+            {
                 throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_NOT_FOUND, "" ), 404 );
             }
-            if (repositoryGroup.getRepositories().stream().noneMatch( r -> repositoryId.equals( r.getId() ) )) {
+            if ( repositoryGroup.getRepositories( ).stream( ).noneMatch( r -> repositoryId.equals( r.getId( ) ) ) )
+            {
                 throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_NOT_FOUND, repositoryId ), 404 );
             }
-            if (!(repositoryGroup instanceof EditableRepositoryGroup)) {
+            if ( !( repositoryGroup instanceof EditableRepositoryGroup ) )
+            {
                 log.error( "This group instance is not editable: {}", repositoryGroupId );
                 throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_UPDATE_FAILED, "" ), 500 );
             }
@@ -305,7 +320,7 @@ public class DefaultRepositoryGroupService implements RepositoryGroupService
         }
         catch ( RepositoryException e )
         {
-            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_UPDATE_FAILED, e.getMessage() ), 500 );
+            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_UPDATE_FAILED, e.getMessage( ) ), 500 );
         }
     }
 
diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/java/org/apache/archiva/rest/services/v2/NativeRepositoryGroupServiceTest.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/java/org/apache/archiva/rest/services/v2/NativeRepositoryGroupServiceTest.java
index 3a5601f..033a4a0 100644
--- a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/java/org/apache/archiva/rest/services/v2/NativeRepositoryGroupServiceTest.java
+++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/test/java/org/apache/archiva/rest/services/v2/NativeRepositoryGroupServiceTest.java
@@ -92,11 +92,11 @@ public class NativeRepositoryGroupServiceTest extends AbstractNativeRestServices
         {
             Map<String, Object> jsonAsMap = new HashMap<>( );
             jsonAsMap.put( "id", "group_001" );
+            jsonAsMap.put( "name", "group_001" );
             Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
                 .when( )
                 .body( jsonAsMap )
                 .post( "" )
-                .prettyPeek()
                 .then( ).statusCode( 201 ).extract( ).response( );
             assertNotNull( response );
             RepositoryGroup result = response.getBody( ).jsonPath( ).getObject( "", RepositoryGroup.class );
@@ -126,10 +126,12 @@ public class NativeRepositoryGroupServiceTest extends AbstractNativeRestServices
         {
             Map<String, Object> jsonAsMap = new HashMap<>( );
             jsonAsMap.put( "id", "group_001" );
+            jsonAsMap.put( "name", "group_001" );
             Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
                 .when( )
                 .body( jsonAsMap )
                 .post( "" )
+                .prettyPeek()
                 .then( ).statusCode( 201 ).extract( ).response( );
             assertNotNull( response );
             response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
@@ -145,8 +147,7 @@ public class NativeRepositoryGroupServiceTest extends AbstractNativeRestServices
         {
             given( ).spec( getRequestSpec( token ) ).contentType( JSON )
                 .when( )
-                .delete( "group_001" )
-                .then( ).statusCode( 200 );
+                .delete( "group_001" );
         }
     }
 
@@ -164,6 +165,7 @@ public class NativeRepositoryGroupServiceTest extends AbstractNativeRestServices
                 groups.add( groupName );
                 Map<String, Object> jsonAsMap = new HashMap<>( );
                 jsonAsMap.put( "id", groupName );
+                jsonAsMap.put( "name", groupName );
                 Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
                     .when( )
                     .body( jsonAsMap )
@@ -186,8 +188,7 @@ public class NativeRepositoryGroupServiceTest extends AbstractNativeRestServices
             {
                 given( ).spec( getRequestSpec( token ) ).contentType( JSON )
                     .when( )
-                    .delete( groupName )
-                    .then( ).statusCode( 200 );
+                    .delete( groupName );
             }
         }
     }
@@ -205,6 +206,7 @@ public class NativeRepositoryGroupServiceTest extends AbstractNativeRestServices
                 groups.add( groupName );
                 Map<String, Object> jsonAsMap = new HashMap<>( );
                 jsonAsMap.put( "id", groupName );
+                jsonAsMap.put( "name", groupName );
                 Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
                     .when( )
                     .body( jsonAsMap )
@@ -251,8 +253,7 @@ public class NativeRepositoryGroupServiceTest extends AbstractNativeRestServices
                 {
                     given( ).spec( getRequestSpec( token ) ).contentType( JSON )
                         .when( )
-                        .delete( groupName )
-                        .then( ).statusCode( 200 );
+                        .delete( groupName );
                 }
             }
         }
@@ -267,6 +268,7 @@ public class NativeRepositoryGroupServiceTest extends AbstractNativeRestServices
         {
             Map<String, Object> jsonAsMap = new HashMap<>( );
             jsonAsMap.put( "id", "group_001" );
+            jsonAsMap.put( "name", "group_001" );
             Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
                 .when( )
                 .body( jsonAsMap )
@@ -302,8 +304,7 @@ public class NativeRepositoryGroupServiceTest extends AbstractNativeRestServices
         {
             given( ).spec( getRequestSpec( token ) ).contentType( JSON )
                 .when( )
-                .delete( "group_001" )
-                .then( ).statusCode( 200 );
+                .delete( "group_001" );
         }
     }
 
@@ -315,6 +316,7 @@ public class NativeRepositoryGroupServiceTest extends AbstractNativeRestServices
         {
             Map<String, Object> jsonAsMap = new HashMap<>( );
             jsonAsMap.put( "id", "group_001" );
+            jsonAsMap.put( "name", "group_001" );
             Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
                 .when( )
                 .body( jsonAsMap )
@@ -371,6 +373,7 @@ public class NativeRepositoryGroupServiceTest extends AbstractNativeRestServices
         {
             Map<String, Object> jsonAsMap = new HashMap<>( );
             jsonAsMap.put( "id", "group_001" );
+            jsonAsMap.put( "name", "group_001" );
             jsonAsMap.put( "repositories", Arrays.asList( "internal" ) );
             Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
                 .when( )
@@ -423,6 +426,7 @@ public class NativeRepositoryGroupServiceTest extends AbstractNativeRestServices
         {
             Map<String, Object> jsonAsMap = new HashMap<>( );
             jsonAsMap.put( "id", "group_001" );
+            jsonAsMap.put( "name", "group_001" );
             jsonAsMap.put( "repositories", Arrays.asList( "internal" ) );
             Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
                 .when( )
@@ -460,8 +464,7 @@ public class NativeRepositoryGroupServiceTest extends AbstractNativeRestServices
         {
             given( ).spec( getRequestSpec( token ) ).contentType( JSON )
                 .when( )
-                .delete( "group_001" )
-                .then( ).statusCode( 200 );
+                .delete( "group_001" );
         }
     }