]> source.dussan.org Git - archiva.git/commitdiff
Changing repository group handling
authorMartin Stockhammer <martin_s@apache.org>
Sun, 27 Jun 2021 09:35:23 +0000 (11:35 +0200)
committerMartin Stockhammer <martin_s@apache.org>
Sun, 27 Jun 2021 09:35:23 +0000 (11:35 +0200)
26 files changed:
archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/archiva/configuration/RepositoryGroupConfiguration.java
archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/archiva/configuration/io/registry/ConfigurationRegistryReader.java
archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/archiva/configuration/io/registry/ConfigurationRegistryWriter.java
archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/CheckedResult.java [new file with mode: 0644]
archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/RepositoryHandler.java [new file with mode: 0644]
archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/RepositoryRegistry.java
archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/RepositoryType.java
archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/validation/AbstractRepositoryValidator.java [new file with mode: 0644]
archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/validation/RepositoryChecker.java [new file with mode: 0644]
archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/validation/RepositoryValidator.java [new file with mode: 0644]
archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/validation/ValidationError.java [new file with mode: 0644]
archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/validation/ValidationResponse.java [new file with mode: 0644]
archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/ArchivaRepositoryRegistry.java
archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/ConfigurationHandler.java
archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/RepositoryGroupHandler.java
archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/validation/CommonGroupValidator.java [new file with mode: 0644]
archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/base/ArchivaRepositoryRegistryTest.java
archiva-modules/archiva-maven/archiva-maven-repository/src/main/java/org/apache/archiva/repository/maven/MavenRepositoryProvider.java
archiva-modules/archiva-maven/archiva-maven-repository/src/test/java/org/apache/archiva/repository/maven/mock/RepositoryRegistryMock.java
archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/RepositoryGroup.java
archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/ValidationError.java [new file with mode: 0644]
archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/ArchivaRestError.java
archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/ErrorKeys.java
archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/RepositoryGroupService.java
archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/ValidationException.java [new file with mode: 0644]
archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/v2/DefaultRepositoryGroupService.java

index cf518951c01be42980da25225cc682acb04b7ad0..d0dc381e46feac329f319b107b69402a0788d84b 100644 (file)
@@ -78,11 +78,37 @@ public class RepositoryGroupConfiguration
      */
     private List<String> repositories;
 
+    /**
+     * The path for local data
+     */
+    private String location;
+
 
       //-----------/
      //- Methods -/
     //-----------/
 
+
+    /**
+     * Return the local path for group data. If the merged index property is set to a non absolute path,
+     * it is relative to this location.
+     *
+     * @return the path for group data storage
+     */
+    public String getLocation( )
+    {
+        return location;
+    }
+
+    /**
+     * Set the local path for group data
+     * @param location
+     */
+    public void setLocation( String location )
+    {
+        this.location = location;
+    }
+
     /**
      * Method addRepository.
      * 
index f3f0f2ea7e797211f5a02ee55be59d7e7deded8c..27146f5e6ebc65d20dcb13a6c9d7e29b2eea9621 100644 (file)
@@ -753,6 +753,8 @@ public class ConfigurationRegistryReader {
         value.setMergedIndexTtl(mergedIndexTtl);
         //String cronExpression = registry.getString( prefix + "cronExpression", value.getCronExpression() );
 
+        value.setLocation( registry.getString( prefix + "location" ) );
+
         List<String> cronExpressionList = registry.getList(prefix + "cronExpression");
         String cronExpression = value.getCronExpression();
         if (cronExpressionList != null && !cronExpressionList.isEmpty()) {
index 2cbd387b6bd711f2a762c9645a9ffac24741300c..83202f531dc52a8b15746430fb7c0c883b8c52bf 100644 (file)
@@ -425,6 +425,9 @@ public class ConfigurationRegistryWriter {
             if (value.getType() != null) {
                 registry.setString(prefix + "type", value.getType());
             }
+            if (value.getLocation()!=null) {
+                registry.setString( prefix+"location", value.getType( ) );
+            }
             if (value.getMergedIndexPath() != null && !value.getMergedIndexPath().equals(".indexer")
             ) {
                 String mergedIndexPath = "mergedIndexPath";
diff --git a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/CheckedResult.java b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/CheckedResult.java
new file mode 100644 (file)
index 0000000..41961a5
--- /dev/null
@@ -0,0 +1,31 @@
+package org.apache.archiva.repository;
+/*
+ * 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.
+ */
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public interface CheckedResult<R extends Repository, D>
+{
+    R getRepository();
+
+    boolean isValid();
+
+    D getResult();
+
+}
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
new file mode 100644 (file)
index 0000000..49fc4de
--- /dev/null
@@ -0,0 +1,178 @@
+package org.apache.archiva.repository;
+/*
+ * 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.configuration.Configuration;
+import org.apache.archiva.repository.validation.RepositoryChecker;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ *
+ * This is the generic interface that handles different repository flavours.
+ *
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public interface RepositoryHandler<R extends Repository, C>
+{
+
+    /**
+     * Creates 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();
+
+    /**
+     * Creates a new instance without registering and without updating the archiva configuration
+     *
+     * @param type the repository type
+     * @param id the repository identifier
+     * @return the repository instance
+     * @throws RepositoryException if the creation failed
+     */
+    R newInstance(RepositoryType type, String id) throws RepositoryException;
+
+    /**
+     * Creates a new instance and updates the given configuration object.
+     *
+     * @param repositoryConfiguration the configuration instance
+     * @return a newly created instance
+     * @throws RepositoryException if the creation failed
+     */
+    R newInstance( C repositoryConfiguration ) throws RepositoryException;
+
+    /**
+     * Adds the given repository to the registry or replaces a already existing repository in the registry.
+     * If an error occurred during the update, it will revert to the old repository status.
+     *
+     * @param repository the repository
+     * @return the created or updated repository instance
+     * @throws RepositoryException if the update or creation failed
+     */
+    R put( R repository ) throws RepositoryException;
+
+    /**
+     * Adds the repository to the registry, based on the given configuration.
+     * If there is a repository registered with the given id, it is updated.
+     * The archiva configuration is updated. The status is not defined, if an error occurs during update. The
+     * The repository instance is registered and initialized if no error occurs
+     *
+     * @param repositoryConfiguration the repository configuration
+     * @return the updated or created repository instance
+     * @throws RepositoryException if the update or creation failed
+     */
+    R put( C repositoryConfiguration ) throws RepositoryException;
+
+    /**
+     * Adds a repository from the given repository configuration. The changes are stored in
+     * the configuration object. The archiva registry is not updated.
+     * The returned repository instance is a clone of the registered repository instance. It is not registered
+     * and not initialized. References are not updated.
+     *
+     * @param repositoryConfiguration the repository configuration
+     * @param configuration the configuration instance
+     * @return the repository instance that was created or updated
+     * @throws RepositoryException if the update or creation failed
+     */
+    R put( C repositoryConfiguration, Configuration configuration ) throws RepositoryException;
+
+    /**
+     * Adds or updates a repository from the given configuration data. The resulting repository is
+     * checked by the repository checker and the result is returned.
+     * 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
+     * @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;
+
+    /**
+     * Removes the given repository from the registry and updates references and saves the new configuration.
+     *
+     * @param id The repository identifier
+     * @throws RepositoryException if the repository could not be removed
+     */
+    void remove( final String id ) throws RepositoryException;
+
+    /**
+     * 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 configuration the configuration to update
+     * @throws RepositoryException if the repository could not be removed
+     */
+    void remove( String id, Configuration configuration ) throws RepositoryException;
+
+    /**
+     * Returns the repository with the given identifier or <code>null</code>, if it is not registered.
+     *
+     * @param id the repository id
+     * @return if the retrieval failed
+     */
+    R get( String id );
+
+    /**
+     * Clones a given repository without registering.
+     *
+     * @param repo the repository that should be cloned
+     * @return a newly created instance with the same repository data
+     */
+    R clone(R repo) throws RepositoryException;
+
+    /**
+     * Updates the references and stores updates in the given <code>configuration</code> instance.
+     * The references that are updated depend on the concrete repository subclass <code>R</code>.
+     * 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 repositoryConfiguration the repository configuration
+     */
+    void updateReferences( R repo, C repositoryConfiguration ) throws RepositoryException;
+
+    /**
+     * Returns all registered repositories.
+     *
+     * @return the list of repositories
+     */
+    Collection<R> getAll();
+
+    /**
+     * 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);
+
+    /**
+     * Initializes
+     */
+    void init();
+
+    /**
+     * Closes the handler
+     */
+    void close();
+
+}
index bb6bbc98b7f021fd9a62e1ed0329cada689754cb..2a521066662b72ce0e41b8302ccb836fe12fd9fc 100644 (file)
@@ -29,23 +29,31 @@ import org.apache.archiva.indexer.ArchivaIndexManager;
 import org.apache.archiva.indexer.IndexUpdateFailedException;
 import org.apache.archiva.repository.metadata.MetadataReader;
 import org.apache.archiva.repository.storage.StorageAsset;
+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;
 
 /**
  *  Registry for repositories. This is the central entry point for repositories. It provides methods for
  *  retrieving, adding and removing repositories.
  *  <p>
- *  The modification methods addXX and removeXX persist the changes immediately to the configuration. If the
+ *  The modification methods putXX and removeXX without configuration object persist the changes immediately to the archiva configuration. If the
  *  configuration save fails the changes are rolled back.
+ *  </p>
  *  <p>
+ *    The modification methods with configuration object do only update the given configuration. The configuration is not saved.
+ *  </p>
  * @author Martin Stockhammer <martin_s@apache.org>
  */
+@SuppressWarnings( "UnusedReturnValue" )
 public interface RepositoryRegistry extends EventSource
 {
     /**
      * Set the configuration for the registry
-     * @param archivaConfiguration
+     * @param archivaConfiguration the archiva configuration instance
      */
     void setArchivaConfiguration( ArchivaConfiguration archivaConfiguration );
 
@@ -57,7 +65,7 @@ public interface RepositoryRegistry extends EventSource
     ArchivaIndexManager getIndexManager( RepositoryType type );
 
     /**
-     * Returns the metadatareader for the given repository type
+     * Returns the metadata reader for the given repository type
      * @param type the repository type
      * @return the metadata reader instance
      */
@@ -75,63 +83,305 @@ public interface RepositoryRegistry extends EventSource
      */
     Collection<ManagedRepository> getManagedRepositories( );
 
+    /**
+     * Returns a collection of all registered remote repositories
+     * @return the collection of remote repositories
+     */
     Collection<RemoteRepository> getRemoteRepositories( );
 
+    /**
+     * Returns a collection of all registered repository groups.
+     *
+     * @return the collection of repository groups
+     */
     Collection<RepositoryGroup> getRepositoryGroups( );
 
+    /**
+     * Returns the repository (managed, remote, group) with the given id
+     * @param repoId the id of the repository
+     * @return the repository or <code>null</code> if no repository with this ID is registered.
+     */
     Repository getRepository( String repoId );
 
+    /**
+     * Returns the managed repository with the given id
+     * @param repoId the id of the repository
+     * @return the managed repository instance or <code>null</code>, if no managed repository with this ID is registered.
+     */
     ManagedRepository getManagedRepository( String repoId );
 
+    /**
+     * Returns the remote repository with the given id
+     * @param repoId the id of the repository
+     * @return the remote repository instance or <code>null</code>, if no remote repository with this ID is registered.
+     */
     RemoteRepository getRemoteRepository( String repoId );
 
+    /**
+     * Returns the repository group with the given id
+     * @param groupId the id of the repository group
+     * @return the repository group instance or <code>null</code>, if no repository group with this ID is registered.
+     */
     RepositoryGroup getRepositoryGroup( String groupId );
 
+    /**
+     * Returns <code>true</code>, if a repository with the given ID is registered, otherwise <code>false</code>
+     * @param repoId the ID of the repository
+     * @return <code>true</code>, if a repository with the given ID is registered, otherwise <code>false</code>
+     */
+    boolean hasRepository(String repoId);
+
+    /**
+     * Returns <code>true</code>, if a managed repository with the given ID is registered, otherwise <code>false</code>
+     * @param repoId the id of the managed repository
+     * @return <code>true</code>, if a managed repository with the given ID is registered, otherwise <code>false</code>
+     */
+    boolean hasManagedRepository(String repoId);
+
+    /**
+     * Returns <code>true</code>, if a remote repository with the given ID is registered, otherwise <code>false</code>
+     * @param repoId the id of the remote repository
+     * @return <code>true</code>, if a remote repository with the given ID is registered, otherwise <code>false</code>
+     */
+    boolean hasRemoteRepository(String repoId);
+
+    /**
+     * Returns <code>true</code>, if a repository group with the given ID is registered, otherwise <code>false</code>
+     * @param groupId the id of the repository group
+     * @return <code>true</code>, if a repository group with the given ID is registered, otherwise <code>false</code>
+     */
+    boolean hasRepositoryGroup( String groupId );
+
+    /**
+     * Adds or updates the given managed repository. If a managed repository with the given id exists already, it is updated
+     * from the data of the given instance. Otherwise a new repository is created and updated by the data of the given instance.
+     *
+     * The archiva configuration is updated and saved after updating the registered repository instance.
+     *
+     * @param managedRepository the managed repository
+     * @return the repository instance, that was created or updated
+     * @throws RepositoryException if an error occurred while creating or updating the instance
+     */
     ManagedRepository putRepository( ManagedRepository managedRepository ) throws RepositoryException;
 
+    /**
+     * Adds or updates the given managed repository. If a managed repository with the given id exists already, it is updated
+     * from the data of the given configuration. Otherwise a new repository is created and updated by the data of the given configuration.
+     *
+     * The archiva configuration is updated and saved after updating the registered repository instance.
+     *
+     * @param managedRepositoryConfiguration the managed repository configuration
+     * @return the repository instance, that was created or updated
+     * @throws RepositoryException if an error occurred while creating or updating the instance
+     */
     ManagedRepository putRepository( ManagedRepositoryConfiguration managedRepositoryConfiguration ) throws RepositoryException;
 
+    /**
+     * Adds or updates the given managed repository. If a managed repository with the given id exists already, it is updated
+     * from the data of the given configuration. Otherwise a new repository is created and updated by the data of the given configuration.
+     *
+     * This method can be used, if the archiva configuration should not be saved. It will only update the given configuration object.
+     *
+     * @param managedRepositoryConfiguration the managed repository configuration
+     * @param configuration the archiva configuration that is updated
+     * @return the repository instance, that was created or updated
+     * @throws RepositoryException if an error occurred while creating or updating the instance
+     */
     ManagedRepository putRepository( ManagedRepositoryConfiguration managedRepositoryConfiguration, Configuration configuration ) throws RepositoryException;
 
+    /**
+     * Adds or updates the given repository group. If a repository group with the given id exists already, it is updated
+     * from the data of the given instance. Otherwise a new repository is created and updated by the data of the given instance.
+     *
+     * The archiva configuration is updated and saved after updating the registered repository instance.
+     *
+     * @param repositoryGroup the repository group
+     * @return the repository instance, that was created or updated
+     * @throws RepositoryException if an error occurred while creating or updating the instance
+     */
     RepositoryGroup putRepositoryGroup( RepositoryGroup repositoryGroup ) throws RepositoryException;
 
+    /**
+     * Adds or updates the given repository group. If a repository group with the given id exists already, it is updated
+     * from the data of the given configuration. Otherwise a new repository is created and updated by the data of the given configuration.
+     *
+     * The archiva configuration is updated and saved after updating the registered repository instance.
+     *
+     * @param repositoryGroupConfiguration the repository group configuration
+     * @return the repository instance, that was created or updated
+     * @throws RepositoryException if an error occurred while creating or updating the instance
+     */
     RepositoryGroup putRepositoryGroup( RepositoryGroupConfiguration repositoryGroupConfiguration ) throws RepositoryException;
 
+
+    /**
+     * This method creates or updates a repository by the given configuration. It uses the <code>validator</code> to check the
+     * result. If the validation is not successful, the repository will not be saved.
+     *
+     * @param configuration the repository configuration
+     * @return the result
+     */
+    CheckedResult<RepositoryGroup, Map<String, List<ValidationError>>> putRepositoryGroupAndValidate( RepositoryGroupConfiguration configuration) throws RepositoryException;
+
+    /**
+     * Adds or updates the given repository group. If a repository group with the given id exists already, it is updated
+     * from the data of the given configuration. Otherwise a new repository is created and updated by the data of the given configuration.
+     *
+     * This method can be used, if the archiva configuration should not be saved. It will only update the given configuration object.
+     *
+     * @param repositoryGroupConfiguration the repository group configuration
+     * @param configuration the archiva configuration that is updated
+     * @return the repository instance, that was created or updated
+     * @throws RepositoryException if an error occurred while creating or updating the instance
+     */
     RepositoryGroup putRepositoryGroup( RepositoryGroupConfiguration repositoryGroupConfiguration, Configuration configuration ) throws RepositoryException;
 
+    /**
+     * Adds or updates the given remote repository. If a remote repository with the given id exists already, it is updated
+     * from the data of the given instance. Otherwise a new repository is created and updated by the data of the given instance.
+     *
+     * This method can be used, if the archiva configuration should not be saved. It will only update the given configuration object.
+     *
+     * @param remoteRepository the remote repository
+     * @param configuration the configuration that is updated
+     * @return the repository instance, that was created or updated
+     * @throws RepositoryException if an error occurred while creating or updating the instance
+     */
     RemoteRepository putRepository( RemoteRepository remoteRepository, Configuration configuration ) throws RepositoryException;
 
+    /**
+     * Adds or updates the given remote repository. If a remote repository with the given id exists already, it is updated
+     * from the data of the given instance. Otherwise a new repository is created and updated by the data of the given instance.
+     *
+     * The archiva configuration is updated and saved after updating the registered repository instance.
+     *
+     * @param remoteRepository the remote repository
+     * @return the repository instance, that was created or updated
+     * @throws RepositoryException if an error occurred while creating or updating the instance
+     */
     RemoteRepository putRepository( RemoteRepository remoteRepository ) throws RepositoryException;
 
+    /**
+     * Adds or updates the given remote repository. If a remote repository with the given id exists already, it is updated
+     * from the data of the given configuration. Otherwise a new repository is created and updated by the data of the given configuration.
+     *
+     * The archiva configuration is updated and saved after updating the registered repository instance.
+     *
+     * @param remoteRepositoryConfiguration the remote repository configuration
+     * @return the repository instance, that was created or updated
+     * @throws RepositoryException if an error occurred while creating or updating the instance
+     */
     RemoteRepository putRepository( RemoteRepositoryConfiguration remoteRepositoryConfiguration ) throws RepositoryException;
 
+    /**
+     * Adds or updates the given remote repository. If a remote repository with the given id exists already, it is updated
+     * from the data of the given configuration. Otherwise a new repository is created and updated by the data of the given configuration.
+     *
+     * This method can be used, if the archiva configuration should not be saved. It will only update the given configuration object.
+     *
+     * @param remoteRepositoryConfiguration the remote repository configuration
+     * @param configuration the archiva configuration where the updated data is stored into
+     * @return the repository instance, that was created or updated
+     * @throws RepositoryException if an error occurred while creating or updating the instance
+     */
     RemoteRepository putRepository( RemoteRepositoryConfiguration remoteRepositoryConfiguration, Configuration configuration ) throws RepositoryException;
 
+    /**
+     * Removes the repository or repository group with the given id, if it exists. Otherwise, it will do nothing.
+     *
+     * The configuration is updated and saved, if the deletion was successful
+     *
+     * @param repoId the id of the repository or repository group to delete
+     * @throws RepositoryException if the repository deletion failed
+     */
     void removeRepository( String repoId ) throws RepositoryException;
 
+    /**
+     * Removes the given repository.
+     *
+     * The configuration is updated and saved, if the deletion was successful
+     *
+     * @param repo the repository instance that should be deleted
+     * @throws RepositoryException if the repository deletion failed
+     */
     void removeRepository( Repository repo ) throws RepositoryException;
 
+    /**
+     * Removes the given managed repository.
+     *
+     * The configuration is updated and saved, if the deletion was successful
+     *
+     * @param managedRepository the managed repository to remove
+     * @throws RepositoryException if the repository deletion failed
+     */
     void removeRepository( ManagedRepository managedRepository ) throws RepositoryException;
 
+    /**
+     * Removes the given managed repository. The given configuration instance is updated, but the
+     * archiva configuration is not saved.
+     *
+     * @param managedRepository the managed repository to remove
+     * @param configuration the configuration instance to update
+     * @throws RepositoryException if the repository deletion failed
+     */
     void removeRepository( ManagedRepository managedRepository, Configuration configuration ) throws RepositoryException;
 
+    /**
+     * Removes the given repository group.
+     *
+     * The configuration is updated and saved, if the deletion was successful
+     *
+     * @param repositoryGroup the repository group to remove
+     * @throws RepositoryException if the repository deletion failed
+     */
     void removeRepositoryGroup( RepositoryGroup repositoryGroup ) throws RepositoryException;
 
+    /**
+     * Removes the given repository group. The given configuration instance is updated, but the
+     * archiva configuration is not saved.
+     *
+     * @param repositoryGroup the repository group to remove
+     * @param configuration the configuration instance to update
+     * @throws RepositoryException if the repository deletion failed
+     */
     void removeRepositoryGroup( RepositoryGroup repositoryGroup, Configuration configuration ) throws RepositoryException;
 
+    /**
+     * Removes the given remote repository.
+     *
+     * The configuration is updated and saved, if the deletion was successful
+     *
+     * @param remoteRepository the remote repository to remove
+     * @throws RepositoryException if the repository deletion failed
+     */
     void removeRepository( RemoteRepository remoteRepository ) throws RepositoryException;
 
+    /**
+     * Removes the given remote repository. The given configuration instance is updated, but the
+     * archiva configuration is not saved.
+     *
+     * @param remoteRepository the remote repository to remove
+     * @param configuration the configuration instance to update
+     * @throws RepositoryException if the repository deletion failed
+     */
     void removeRepository( RemoteRepository remoteRepository, Configuration configuration ) throws RepositoryException;
 
+    /**
+     * Reloads all repositories and groups from the configuration
+     */
     void reload( );
 
     void resetIndexingContext( Repository repository ) throws IndexUpdateFailedException;
 
-    ManagedRepository clone( ManagedRepository repo, String newId ) throws RepositoryException;
-
-    <T extends Repository> Repository clone( T repo, String newId ) throws RepositoryException;
-
-    RemoteRepository clone( RemoteRepository repo, String newId ) throws RepositoryException;
+    /**
+     * Creates a new repository based on the given repository and with the given new id.
+     * @param repo the repository to copy from
+     * @param newId the new repository id
+     * @param <T> the type of the repository (Manage, Remote or RepositoryGroup)
+     * @return the newly created repository
+     * @throws RepositoryException if the repository could not be created
+     */
+    <T extends Repository> T clone( T repo, String newId ) throws RepositoryException;
 
     /**
      * Return the repository that stores the given asset.
@@ -139,4 +389,25 @@ public interface RepositoryRegistry extends EventSource
      * @return the repository or <code>null</code> if no matching repository is found
      */
     Repository getRepositoryOfAsset( StorageAsset asset );
+
+    /**
+     * Validates the set attributes of the given repository instance and returns the validation result.
+     * The repository registry uses all available validators and applies their validateRepository method to the given
+     * repository. Validation results will be merged per field.
+     *
+     * @param repository the repository to validate against
+     * @return the result of the validation.
+     */
+    <R extends Repository> ValidationResponse<R> validateRepository( R repository);
+
+    /**
+     * Validates the set attributes of the given repository instance for a repository update and returns the validation result.
+     * The repository registry uses all available validators and applies their validateRepositoryForUpdate method to the given
+     * repository. Validation results will be merged per field.
+     *
+     * @param repository the repository to validate against
+     * @return the result of the validation.
+     */
+    <R extends Repository> ValidationResponse<R> validateRepositoryForUpdate( R repository);
+
 }
diff --git a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/validation/AbstractRepositoryValidator.java b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/validation/AbstractRepositoryValidator.java
new file mode 100644 (file)
index 0000000..319fc50
--- /dev/null
@@ -0,0 +1,50 @@
+package org.apache.archiva.repository.validation;
+/*
+ * 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.CheckedResult;
+import org.apache.archiva.repository.Repository;
+import org.apache.archiva.repository.RepositoryRegistry;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public abstract class AbstractRepositoryValidator<R extends Repository> implements RepositoryValidator<R>
+{
+    protected RepositoryRegistry repositoryRegistry;
+
+    @Override
+    public void setRepositoryRegistry( RepositoryRegistry repositoryRegistry )
+    {
+        this.repositoryRegistry = repositoryRegistry;
+    }
+
+    protected abstract ValidationResponse<R> apply( R repo, boolean update );
+
+    @Override
+    public ValidationResponse<R> apply( R r )
+    {
+        return apply( r, false );
+    }
+
+    @Override
+    public ValidationResponse<R> applyForUpdate( R repo )
+    {
+        return apply( repo, true );
+    }
+}
diff --git a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/validation/RepositoryChecker.java b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/validation/RepositoryChecker.java
new file mode 100644 (file)
index 0000000..f208d10
--- /dev/null
@@ -0,0 +1,36 @@
+package org.apache.archiva.repository.validation;
+/*
+ * 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.CheckedResult;
+import org.apache.archiva.repository.Repository;
+
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public interface RepositoryChecker<R extends Repository, D> extends Function<R, CheckedResult<R,D>>
+{
+
+    @Override
+    CheckedResult<R,D> apply( R r );
+
+    CheckedResult<R,D> applyForUpdate( R repo );
+}
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
new file mode 100644 (file)
index 0000000..041fe8a
--- /dev/null
@@ -0,0 +1,85 @@
+package org.apache.archiva.repository.validation;
+/*
+ * 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.RepositoryRegistry;
+import org.apache.archiva.repository.RepositoryType;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * A repository validator validates given repository data against certain rules.
+ *
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public interface RepositoryValidator<R extends Repository> extends RepositoryChecker<R, Map<String, List<ValidationError>>>, Comparable<RepositoryValidator<R>>
+{
+
+    int DEFAULT_PRIORITY=1000;
+
+    /**
+     * Returns the repository type for which this validator can be used. If the validator is applicable
+     * to all types, it should return {@link RepositoryType#ALL}
+     *
+     * @return the repository type for which this validator is applicable
+     */
+    default RepositoryType getType() {
+        return RepositoryType.ALL;
+    }
+
+    /**
+     * Returns the priority of this validator. Smaller values mean higher priority.
+     * All common validators have priority {@link #DEFAULT_PRIORITY}
+     *
+     * Validators are called in numerical order of their priority.
+     *
+     * @return
+     */
+    default int getPriority() {
+        return DEFAULT_PRIORITY;
+    }
+
+
+    /**
+     * Orders by priority
+     *
+     * @see Comparable#compareTo(Object)
+     */
+    @Override
+    default int compareTo( RepositoryValidator o ) {
+        if (o==null) {
+            return 1;
+        } else
+        {
+            return this.getPriority( ) - o.getPriority( );
+        }
+    }
+
+    /**
+     * Sets the repository registry to the given instance.
+     * @param repositoryRegistry the repository registry
+     */
+    void setRepositoryRegistry( RepositoryRegistry repositoryRegistry );
+
+    Class<R> getFlavour();
+
+    boolean isFlavour(Class<?> clazz);
+}
diff --git a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/validation/ValidationError.java b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/validation/ValidationError.java
new file mode 100644 (file)
index 0000000..22594fb
--- /dev/null
@@ -0,0 +1,186 @@
+package org.apache.archiva.repository.validation;
+/*
+ * 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.commons.lang3.StringUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Represents a single validation error. A error is defined by a global unique key and has a optional number
+ * of arguments.
+ * <p>
+ * The unique key should represent a category, the attribute and a generic type, separated by '.'
+ * E.g. repository_group.id.empty
+ * </p>
+ * <p>
+ * Categories normally separate errors for different domain types, like managed repository, repository group, maven repository.
+ * <p>
+ * Types define a certain type of error that can be handled similar independent of the attribute or category
+ *
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public class ValidationError
+{
+    public static final String UNSPECIFIED = "unspecified";
+
+    final String errorKey;
+    final String attribute;
+    final String category;
+    final String type;
+    final List<Object> arguments = new ArrayList<>();
+
+
+    public static ValidationError ofKey( final String errorKey, Object... arguments )
+    {
+        return new ValidationError( errorKey, getCategoryFromKey( errorKey ), getTypeFromKey( errorKey ), getAttributeFromKey( errorKey ),
+            Arrays.asList( arguments ) );
+    }
+
+    public static ValidationError ofKey( String errorKey, List<Object> arguments )
+    {
+        return new ValidationError( errorKey, getCategoryFromKey( errorKey ), getTypeFromKey( errorKey ), getAttributeFromKey( errorKey ),
+            arguments );
+    }
+
+    public ValidationError( String errorKey, String category, String type, String attribute, List<Object> arguments )
+    {
+        if ( StringUtils.isEmpty( errorKey ) )
+        {
+            throw new IllegalArgumentException( "The key of a validation error cannot be empty" );
+        }
+        this.errorKey = errorKey;
+        if ( arguments != null )
+        {
+            this.arguments.addAll( arguments );
+        }
+        this.type = type;
+        this.category = category;
+        this.attribute = attribute;
+    }
+
+    private static String getTypeFromKey( final String errorKey )
+    {
+        return errorKey.contains( "." ) ?
+            StringUtils.substringAfterLast( errorKey, "." ) :
+            UNSPECIFIED;
+    }
+
+    private static String getCategoryFromKey( final String errorKey )
+    {
+        return errorKey.contains( "." ) ?
+            StringUtils.substringBefore( errorKey, "." ) :
+            UNSPECIFIED;
+    }
+
+    private static String getAttributeFromKey( final String errorKey )
+    {
+        return StringUtils.countMatches( errorKey, "." ) >= 2 ?
+            StringUtils.substringBetween( errorKey, "." ) : UNSPECIFIED;
+    }
+
+    /**
+     * Returns the unique key of this validation error. It is best practice for keys to contain the
+     * validation source, the attribute and a unique error definition.
+     * E.g. repository_group.id.empty
+     *
+     * @return
+     */
+    public String getErrorKey( )
+    {
+        return errorKey;
+    }
+
+    /**
+     * Returns the list of arguments stored for this error
+     * @return the list of arguments
+     */
+    public List<Object> getArguments( )
+    {
+        return arguments;
+    }
+
+    /**
+     * Adds the given argument to the list
+     * @param argument the argument to add
+     */
+    public void addArgument( Object argument )
+    {
+        this.arguments.add( argument );
+    }
+
+    /**
+     * Returns the generic error type, this error represents.
+     *
+     * @return the error type or {@link #UNSPECIFIED} if not explicitly set.
+     */
+    public String getType( )
+    {
+        return type;
+    }
+
+    /**
+     * Returns the category of the error.
+     *
+     * @return the category or {@link #UNSPECIFIED} if not explicitly set
+     */
+    public String getCategory( )
+    {
+        return category;
+    }
+
+    /**
+     * Returns the attribute name
+     * @return the attribute name or {@link #UNSPECIFIED} if not explicitly set
+     */
+    public String getAttribute( )
+    {
+        return attribute;
+    }
+
+    @Override
+    public String toString( )
+    {
+        final StringBuilder sb = new StringBuilder( "ValidationError{" );
+        sb.append( "errorKey='" ).append( errorKey ).append( '\'' );
+        sb.append( '}' );
+        return sb.toString( );
+    }
+
+    @Override
+    public boolean equals( Object o )
+    {
+        if ( this == o ) return true;
+        if ( o == null || getClass( ) != o.getClass( ) ) return false;
+
+        ValidationError that = (ValidationError) o;
+
+        if ( !errorKey.equals( that.errorKey ) ) return false;
+        return arguments.equals( that.arguments );
+    }
+
+    @Override
+    public int hashCode( )
+    {
+        int result = errorKey.hashCode( );
+        result = 31 * result + arguments.hashCode( );
+        return result;
+    }
+}
diff --git a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/validation/ValidationResponse.java b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/validation/ValidationResponse.java
new file mode 100644 (file)
index 0000000..377a3cc
--- /dev/null
@@ -0,0 +1,110 @@
+package org.apache.archiva.repository.validation;
+/*
+ * 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.CheckedResult;
+import org.apache.archiva.repository.Repository;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * A validation response gives information about the validation status for certain attributes.
+ *
+ *
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public class ValidationResponse<R extends Repository> implements CheckedResult<R, Map<String, List<ValidationError>>>
+{
+    final boolean valid;
+    final R repository;
+    final Map<String, List<ValidationError>> validationErrors = new HashMap<>( );
+
+
+    public ValidationResponse( R repo, Map<String, List<ValidationError>> errors)
+    {
+        if( errors==null || errors.size()==0 ) {
+            this.valid = true;
+        } else {
+            this.valid = false;
+            validationErrors.putAll( errors );
+        }
+        this.repository = repo;
+    }
+
+    public static <S extends Repository> ValidationResponse<S> getValid( S repository )
+    {
+        return new ValidationResponse<>( repository, null );
+    }
+
+    @Override
+    public R getRepository( )
+    {
+        return repository;
+    }
+
+    /**
+     * Returns true, if the validation was successful and there are not validation errors.
+     * @return <code>true</code>, if the validation was successful, otherwise <code>false</code>
+     */
+    @Override
+    public boolean isValid( )
+    {
+        return valid;
+    }
+
+    @Override
+    public Map<String, List<ValidationError>> getResult( )
+    {
+        return validationErrors;
+    }
+
+
+    /**
+     * Add the given validation error to the list for the given attribute.
+     *
+     * @param attribute the name of the attribute
+     * @param error the error that is added to the list
+     */
+    public void addValidationError(String attribute, ValidationError error) {
+        if (!validationErrors.containsKey( attribute )) {
+            validationErrors.put( attribute, new ArrayList<>( ) );
+        }
+        validationErrors.get( attribute ).add( error );
+    }
+
+    /**
+     * Returns a list of validation errors that are stored for the given attribute. If there are no
+     * errors stored for this attribute, a empty list is returned.
+     *
+     * @param attribute the name of the attribute
+     * @return the list of validation errors
+     */
+    public List<ValidationError> getValidationErrors(String attribute) {
+        if (validationErrors.containsKey( attribute )) {
+            return validationErrors.get( attribute );
+        } else {
+            return Collections.emptyList( );
+        }
+    }
+
+}
index 24943609202a1afad4ec25ad8377027afba94b17..db1d626a23bcaf25bdb7a5c580331fe5c3d6692e 100644 (file)
@@ -38,6 +38,7 @@ import org.apache.archiva.indexer.ArchivaIndexingContext;
 import org.apache.archiva.indexer.IndexCreationFailedException;
 import org.apache.archiva.indexer.IndexManagerFactory;
 import org.apache.archiva.indexer.IndexUpdateFailedException;
+import org.apache.archiva.repository.CheckedResult;
 import org.apache.archiva.repository.EditableManagedRepository;
 import org.apache.archiva.repository.EditableRemoteRepository;
 import org.apache.archiva.repository.EditableRepository;
@@ -59,6 +60,11 @@ import org.apache.archiva.repository.features.IndexCreationFeature;
 import org.apache.archiva.repository.features.StagingRepositoryFeature;
 import org.apache.archiva.repository.metadata.MetadataReader;
 import org.apache.archiva.repository.storage.StorageAsset;
+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.collections4.ListUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -76,6 +82,7 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeSet;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 import java.util.stream.Collectors;
@@ -98,7 +105,6 @@ public class ArchivaRepositoryRegistry implements ConfigurationListener, EventHa
 {
 
     private static final Logger log = LoggerFactory.getLogger( RepositoryRegistry.class );
-    private final ConfigurationHandler configurationHandler;
 
     /**
      * We inject all repository providers
@@ -129,16 +135,52 @@ public class ArchivaRepositoryRegistry implements ConfigurationListener, EventHa
     private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock( );
 
     private RepositoryGroupHandler groupHandler;
+    private final Set<RepositoryValidator<? extends Repository>> validators;
+    private final RepositoryChecker<RepositoryGroup, Map<String, List<ValidationError>>> groupChecker;
+    private final RepositoryChecker<ManagedRepository, Map<String, List<ValidationError>>> managedChecker;
+    private final RepositoryChecker<RemoteRepository, Map<String, List<ValidationError>>> remoteChecker;
+    private final ConfigurationHandler configurationHandler;
+
 
     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 )
+    public ArchivaRepositoryRegistry( ConfigurationHandler configurationHandler, List<RepositoryValidator<? extends Repository>> validatorList )
     {
         this.eventManager = new EventManager( this );
         this.configurationHandler = configurationHandler;
+        this.validators = initValidatorList( validatorList );
+        this.groupChecker = initChecker( RepositoryGroup.class );
+        this.managedChecker = initChecker( ManagedRepository.class );
+        this.remoteChecker = initChecker( RemoteRepository.class );
+    }
+
+    private <R extends Repository> RepositoryChecker<R, Map<String, List<ValidationError>>> initChecker(Class<R> clazz) {
+        return new RepositoryChecker<R, Map<String, List<ValidationError>>>( )
+        {
+            @Override
+            public CheckedResult<R, Map<String, List<ValidationError>>> apply( R repositoryGroup )
+            {
+                return this.apply( repositoryGroup );
+            }
+
+            @Override
+            public CheckedResult<R, Map<String, List<ValidationError>>> applyForUpdate( R repo )
+            {
+                return this.applyForUpdate( repo );
+            }
+        };
+    }
+
+    private Set<RepositoryValidator<? extends Repository>> initValidatorList( List<RepositoryValidator<? extends Repository>> validators )
+    {
+        TreeSet<RepositoryValidator<? extends Repository>> val = new TreeSet<>( );
+        for (RepositoryValidator<? extends Repository> validator : validators) {
+            val.add( validator );
+        }
+        return val;
     }
 
     @Override
@@ -513,7 +555,7 @@ public class ArchivaRepositoryRegistry implements ConfigurationListener, EventHa
         rwLock.readLock( ).lock( );
         try
         {
-            return groupHandler.getRepositoryGroups( );
+            return groupHandler.getAll( );
         }
         finally
         {
@@ -545,9 +587,9 @@ public class ArchivaRepositoryRegistry implements ConfigurationListener, EventHa
                 log.debug( "Remote repo" );
                 return remoteRepositories.get( repoId );
             }
-            else if ( groupHandler.hasRepositoryGroup( repoId ) )
+            else if ( groupHandler.has( repoId ) )
             {
-                return groupHandler.getRepositoryGroup( repoId );
+                return groupHandler.get( repoId );
             }
             else
             {
@@ -608,7 +650,7 @@ public class ArchivaRepositoryRegistry implements ConfigurationListener, EventHa
         rwLock.readLock( ).lock( );
         try
         {
-            return groupHandler.getRepositoryGroup( groupId );
+            return groupHandler.get( groupId );
         }
         finally
         {
@@ -616,6 +658,30 @@ public class ArchivaRepositoryRegistry implements ConfigurationListener, EventHa
         }
     }
 
+    @Override
+    public boolean hasRepository( String repoId )
+    {
+        return this.managedRepositories.containsKey( repoId ) || this.remoteRepositories.containsKey( repoId ) || groupHandler.has( repoId );
+    }
+
+    @Override
+    public boolean hasManagedRepository( String repoId )
+    {
+        return this.managedRepositories.containsKey( repoId );
+    }
+
+    @Override
+    public boolean hasRemoteRepository( String repoId )
+    {
+        return this.remoteRepositories.containsKey( repoId );
+    }
+
+    @Override
+    public boolean hasRepositoryGroup( String groupId )
+    {
+        return groupHandler.has( groupId );
+    }
+
     protected void saveConfiguration( Configuration configuration ) throws IndeterminateConfigurationException, RegistryException
     {
         configurationHandler.save( configuration, ConfigurationHandler.REGISTRY_EVENT_TAG );
@@ -805,7 +871,7 @@ public class ArchivaRepositoryRegistry implements ConfigurationListener, EventHa
             {
                 throw new RepositoryException( "Fatal error. RepositoryGroupHandler not registered!" );
             }
-            return this.groupHandler.putRepositoryGroup( repositoryGroup );
+            return this.groupHandler.put( repositoryGroup );
         }
         finally
         {
@@ -827,7 +893,7 @@ public class ArchivaRepositoryRegistry implements ConfigurationListener, EventHa
         rwLock.writeLock( ).lock( );
         try
         {
-            return groupHandler.putRepositoryGroup( repositoryGroupConfiguration );
+            return groupHandler.put( repositoryGroupConfiguration );
         }
         finally
         {
@@ -836,6 +902,21 @@ public class ArchivaRepositoryRegistry implements ConfigurationListener, EventHa
 
     }
 
+    @Override
+    public CheckedResult<RepositoryGroup, Map<String, List<ValidationError>>> putRepositoryGroupAndValidate( RepositoryGroupConfiguration repositoryGroupConfiguration )
+        throws RepositoryException
+    {
+        rwLock.writeLock( ).lock( );
+        try
+        {
+            return groupHandler.putWithCheck( repositoryGroupConfiguration, this.groupChecker );
+        }
+        finally
+        {
+            rwLock.writeLock( ).unlock( );
+        }
+    }
+
     /**
      * Adds a new repository group or updates the repository group with the same id. The given configuration object is updated, but
      * the configuration is not saved.
@@ -851,7 +932,7 @@ public class ArchivaRepositoryRegistry implements ConfigurationListener, EventHa
         rwLock.writeLock( ).lock( );
         try
         {
-            return groupHandler.putRepositoryGroup( repositoryGroupConfiguration, configuration );
+            return groupHandler.put( repositoryGroupConfiguration, configuration );
         }
         finally
         {
@@ -1234,12 +1315,12 @@ public class ArchivaRepositoryRegistry implements ConfigurationListener, EventHa
             return;
         }
         final String id = repositoryGroup.getId( );
-        if ( groupHandler.hasRepositoryGroup( id ) )
+        if ( groupHandler.has( id ) )
         {
             rwLock.writeLock( ).lock( );
             try
             {
-                groupHandler.removeRepositoryGroup( id );
+                groupHandler.remove( id );
             }
             finally
             {
@@ -1256,12 +1337,12 @@ public class ArchivaRepositoryRegistry implements ConfigurationListener, EventHa
             return;
         }
         final String id = repositoryGroup.getId( );
-        if ( groupHandler.hasRepositoryGroup( id ) )
+        if ( groupHandler.has( id ) )
         {
             rwLock.writeLock( ).lock( );
             try
             {
-                groupHandler.removeRepositoryGroup( id, configuration );
+                groupHandler.remove( id, configuration );
             }
             finally
             {
@@ -1395,7 +1476,6 @@ public class ArchivaRepositoryRegistry implements ConfigurationListener, EventHa
      * @param repo The origin repository
      * @return The cloned repository.
      */
-    @Override
     public ManagedRepository clone( ManagedRepository repo, String newId ) throws RepositoryException
     {
         if ( managedRepositories.containsKey( newId ) || remoteRepositories.containsKey( newId ) )
@@ -1411,15 +1491,15 @@ public class ArchivaRepositoryRegistry implements ConfigurationListener, EventHa
     }
 
     @Override
-    public <T extends Repository> Repository clone( T repo, String newId ) throws RepositoryException
+    public <T extends Repository> T clone( T repo, String newId ) throws RepositoryException
     {
         if ( repo instanceof RemoteRepository )
         {
-            return this.clone( (RemoteRepository) repo, newId );
+            return (T) this.clone( (RemoteRepository) repo, newId );
         }
         else if ( repo instanceof ManagedRepository )
         {
-            return this.clone( (ManagedRepository) repo, newId );
+            return (T) this.clone( (ManagedRepository) repo, newId );
         }
         else
         {
@@ -1434,7 +1514,6 @@ public class ArchivaRepositoryRegistry implements ConfigurationListener, EventHa
      * @param repo The origin repository
      * @return The cloned repository.
      */
-    @Override
     public RemoteRepository clone( RemoteRepository repo, String newId ) throws RepositoryException
     {
         if ( managedRepositories.containsKey( newId ) || remoteRepositories.containsKey( newId ) )
@@ -1463,6 +1542,35 @@ public class ArchivaRepositoryRegistry implements ConfigurationListener, EventHa
         }
     }
 
+    @Override
+    public <R extends Repository> ValidationResponse<R> validateRepository( R repository )
+    {
+        Map<String, List<ValidationError>> errorMap = this.validators.stream( )
+            .filter( ( validator ) -> validator.getType( ).equals( RepositoryType.ALL ) || repository.getType( ).equals( validator.getType( ) ) )
+            .filter( val -> val.isFlavour( repository.getClass() ))
+            .flatMap( validator -> ((RepositoryValidator<R>)validator).apply( repository ).getResult().entrySet( ).stream( ) )
+            .collect( Collectors.toMap(
+                entry -> entry.getKey( ),
+                entry -> entry.getValue( ),
+                ( list1, list2 ) -> ListUtils.union( list1, list2 )
+            ) );
+        return new ValidationResponse( repository, errorMap );
+    }
+
+    @Override
+    public <R extends Repository> ValidationResponse<R> validateRepositoryForUpdate( R repository )
+    {
+        Map<String, List<ValidationError>> errorMap = this.validators.stream( )
+            .filter( ( validator ) -> validator.getType( ).equals( RepositoryType.ALL ) || repository.getType( ).equals( validator.getType( ) ) )
+            .filter( val -> val.isFlavour( repository.getClass() ))
+            .flatMap( validator -> ((RepositoryValidator<R>)validator).applyForUpdate( repository ).getResult().entrySet( ).stream( ) )
+            .collect( Collectors.toMap(
+                entry -> entry.getKey( ),
+                entry -> entry.getValue( ),
+                ( list1, list2 ) -> ListUtils.union( list1, list2 )
+            ) );
+        return new ValidationResponse( repository, errorMap );
+    }
 
     @Override
     public void configurationEvent( ConfigurationEvent event )
@@ -1509,22 +1617,25 @@ public class ArchivaRepositoryRegistry implements ConfigurationListener, EventHa
     {
         RepositoryIndexEvent idxEvent = event;
         EditableRepository repo = (EditableRepository) idxEvent.getRepository( );
-        if ( repo != null ) {
+        if ( repo != null )
+        {
             ArchivaIndexManager idxmgr = getIndexManager( repo.getType( ) );
             if ( repo.getIndexingContext( ) != null )
             {
                 try
                 {
-                        ArchivaIndexingContext newCtx = idxmgr.move( repo.getIndexingContext( ), repo );
-                        repo.setIndexingContext( newCtx );
-                        idxmgr.updateLocalIndexPath( repo );
+                    ArchivaIndexingContext newCtx = idxmgr.move( repo.getIndexingContext( ), repo );
+                    repo.setIndexingContext( newCtx );
+                    idxmgr.updateLocalIndexPath( repo );
 
                 }
                 catch ( IndexCreationFailedException e )
                 {
                     log.error( "Could not move index to new directory: '{}'", e.getMessage( ), e );
                 }
-            } else {
+            }
+            else
+            {
                 try
                 {
                     ArchivaIndexingContext context = idxmgr.createContext( repo );
index c4ee513705e525e77bb1fb46867ae77a4352a949..4c21083e9c852ff18d6c50f110ad61554a00979f 100644 (file)
@@ -24,6 +24,8 @@ import org.apache.archiva.configuration.ConfigurationListener;
 import org.apache.archiva.configuration.IndeterminateConfigurationException;
 import org.springframework.stereotype.Service;
 
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
 /**
  * This is just a simple wrapper to access the archiva configuration used by the registry and associated classes
  *
@@ -36,6 +38,8 @@ public class ConfigurationHandler
 
     private ArchivaConfiguration archivaConfiguration;
 
+    final ReentrantReadWriteLock lock = new ReentrantReadWriteLock( );
+
     public ConfigurationHandler( ArchivaConfiguration archivaConfiguration ) {
         this.archivaConfiguration = archivaConfiguration;
     }
@@ -68,4 +72,7 @@ public class ConfigurationHandler
         archivaConfiguration.save( configuration, "" );
     }
 
+    ReentrantReadWriteLock getLock() {
+        return lock;
+    }
 }
index 9a08e76ae1d25e053595e95d5271bf8fb37519aa..376be6665bcddc87df75ae4344a7387377d2450e 100644 (file)
@@ -20,18 +20,22 @@ package org.apache.archiva.repository.base;
 import org.apache.archiva.components.registry.RegistryException;
 import org.apache.archiva.configuration.Configuration;
 import org.apache.archiva.configuration.IndeterminateConfigurationException;
+import org.apache.archiva.configuration.ManagedRepositoryConfiguration;
 import org.apache.archiva.configuration.RepositoryGroupConfiguration;
 import org.apache.archiva.indexer.merger.MergedRemoteIndexesScheduler;
+import org.apache.archiva.repository.CheckedResult;
 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.RepositoryHandler;
 import org.apache.archiva.repository.RepositoryProvider;
 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.RepositoryChecker;
 import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -49,6 +53,7 @@ import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
 import java.util.stream.Collectors;
 
 import static org.apache.archiva.indexer.ArchivaIndexManager.DEFAULT_INDEX_PATH;
@@ -59,52 +64,60 @@ import static org.apache.archiva.indexer.ArchivaIndexManager.DEFAULT_INDEX_PATH;
  *
  * @author Martin Stockhammer <martin_s@apache.org>
  */
-@Service("repositoryGroupHandler#default")
-public class RepositoryGroupHandler
+@Service( "repositoryGroupHandler#default" )
+public class RepositoryGroupHandler implements RepositoryHandler<RepositoryGroup, RepositoryGroupConfiguration>
 {
-    private static final Logger log = LoggerFactory.getLogger(RepositoryGroupHandler.class);
+    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<>();
+    private final Map<String, RepositoryGroup> repositoryGroups = new HashMap<>( );
 
     private Path groupsDirectory;
 
     /**
      * 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 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) {
+                                   @Named( "mergedRemoteIndexesScheduler#default" ) MergedRemoteIndexesScheduler mergedRemoteIndexesScheduler )
+    {
         this.configurationHandler = configurationHandler;
         this.mergedRemoteIndexesScheduler = mergedRemoteIndexesScheduler;
         this.repositoryRegistry = repositoryRegistry;
     }
 
+    @Override
     @PostConstruct
-    private void init() {
+    public void init( )
+    {
         log.debug( "Initializing repository group handler " + repositoryRegistry.toString( ) );
+        initializeStorage( );
         // We are registering this class on the registry. This is necessary to avoid circular dependencies via injection.
         this.repositoryRegistry.registerGroupHandler( this );
-        initializeStorage();
     }
 
-    public void initializeFromConfig() {
-        this.repositoryGroups.clear();
-        this.repositoryGroups.putAll( getRepositoryGroupsFromConfig( ) );
-        for (RepositoryGroup group : this.repositoryGroups.values()) {
+    public void initializeFromConfig( )
+    {
+        this.repositoryGroups.clear( );
+        this.repositoryGroups.putAll( newInstancesFromConfig( ) );
+        for ( RepositoryGroup group : this.repositoryGroups.values( ) )
+        {
             initializeGroup( group );
         }
     }
 
-    private void initializeStorage() {
+    private void initializeStorage( )
+    {
         Path baseDir = this.configurationHandler.getArchivaConfiguration( ).getRepositoryGroupBaseDir( );
-        if (!Files.exists( baseDir) ) {
+        if ( !Files.exists( baseDir ) )
+        {
             try
             {
                 Files.createDirectories( baseDir );
@@ -117,86 +130,115 @@ public class RepositoryGroupHandler
         this.groupsDirectory = baseDir;
     }
 
-    private void initializeGroup(RepositoryGroup repositoryGroup) {
-        StorageAsset indexDirectoy = getMergedIndexDirectory( repositoryGroup );
-        if (!indexDirectoy.exists()) {
+    private void initializeGroup( RepositoryGroup repositoryGroup )
+    {
+        StorageAsset indexDirectory = getMergedIndexDirectory( repositoryGroup );
+        if ( !indexDirectory.exists( ) )
+        {
             try
             {
-                indexDirectoy.create( );
+                indexDirectory.create( );
             }
             catch ( IOException e )
             {
-                log.error( "Could not create index directory {} for group {}: {}", indexDirectoy, repositoryGroup.getId( ), e.getMessage( ) );
+                log.error( "Could not create index directory {} for group {}: {}", indexDirectory, repositoryGroup.getId( ), e.getMessage( ) );
             }
         }
-        Path groupPath = groupsDirectory.resolve(repositoryGroup.getId() );
-        if ( !Files.exists(groupPath) )
+        Path groupPath = groupsDirectory.resolve( repositoryGroup.getId( ) );
+        if ( !Files.exists( groupPath ) )
         {
-            try {
-                Files.createDirectories(groupPath);
-            } catch (IOException e) {
-                log.error("Could not create repository group directory {}", groupPath);
+            try
+            {
+                Files.createDirectories( groupPath );
+            }
+            catch ( IOException e )
+            {
+                log.error( "Could not create repository group directory {}", groupPath );
             }
         }
         mergedRemoteIndexesScheduler.schedule( repositoryGroup,
-            indexDirectoy);
+            indexDirectory );
     }
 
     public StorageAsset getMergedIndexDirectory( RepositoryGroup group )
     {
-        if (group!=null) {
-            return group.getFeature( IndexCreationFeature.class).get().getLocalIndexPath();
-        } else {
+        if ( group != null )
+        {
+            return group.getFeature( IndexCreationFeature.class ).get( ).getLocalIndexPath( );
+        }
+        else
+        {
             return null;
         }
     }
 
-    public Map<String, RepositoryGroup> getRepositoryGroupsFromConfig() {
-        try {
+
+    @Override
+    public Map<String, RepositoryGroup> newInstancesFromConfig( )
+    {
+        try
+        {
             List<RepositoryGroupConfiguration> repositoryGroupConfigurations =
-                this.configurationHandler.getBaseConfiguration().getRepositoryGroups();
+                this.configurationHandler.getBaseConfiguration( ).getRepositoryGroups( );
 
-            if (repositoryGroupConfigurations == null) {
-                return Collections.emptyMap();
+            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);
+            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();
+        }
+        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
+    @Override
+    public RepositoryGroup newInstance( final RepositoryType type, String id ) throws RepositoryException
     {
-        RepositoryGroup repositoryGroup = provider.createRepositoryGroup(config);
-        repositoryGroup.registerEventHandler( RepositoryEvent.ANY, repositoryRegistry);
-        updateRepositoryReferences(provider, repositoryGroup, config);
-        return repositoryGroup;
+        RepositoryProvider provider = repositoryRegistry.getProvider( type );
+        RepositoryGroupConfiguration config = new RepositoryGroupConfiguration( );
+        config.setId( id );
+        return createNewRepositoryGroup( provider, config );
     }
 
-    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()));
-        }
+    @Override
+    public RepositoryGroup newInstance( final RepositoryGroupConfiguration repositoryConfiguration ) throws RepositoryException
+    {
+        RepositoryType type = RepositoryType.valueOf( repositoryConfiguration.getType( ) );
+        RepositoryProvider provider = repositoryRegistry.getProvider( type );
+        return createNewRepositoryGroup( provider, repositoryConfiguration );
     }
 
+    private RepositoryGroup createNewRepositoryGroup( RepositoryProvider provider, RepositoryGroupConfiguration config ) throws RepositoryException
+    {
+        RepositoryGroup repositoryGroup = provider.createRepositoryGroup( config );
+        updateReferences( repositoryGroup, config );
+        return repositoryGroup;
+    }
+
+
     /**
      * Adds a new repository group to the current list, or replaces the repository group definition with
      * the same id, if it exists already.
@@ -205,36 +247,56 @@ public class RepositoryGroupHandler
      * @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) {
-                    this.mergedRemoteIndexesScheduler.unschedule( originRepoGroup );
-                    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);
+    @Override
+    public RepositoryGroup put( final RepositoryGroup repositoryGroup ) throws RepositoryException
+    {
+        final String id = repositoryGroup.getId( );
+        RepositoryGroup originRepoGroup = repositoryGroups.remove( id );
+        try
+        {
+            if ( originRepoGroup != null && originRepoGroup != repositoryGroup )
+            {
+                this.mergedRemoteIndexesScheduler.unschedule( originRepoGroup );
+                originRepoGroup.close( );
+            }
+            RepositoryProvider provider = repositoryRegistry.getProvider( repositoryGroup.getType( ) );
+            RepositoryGroupConfiguration newCfg = provider.getRepositoryGroupConfiguration( repositoryGroup );
+            ReentrantReadWriteLock.WriteLock configLock = this.configurationHandler.getLock( ).writeLock( );
+            configLock.lock( );
+            try
+            {
+                Configuration configuration = this.configurationHandler.getBaseConfiguration( );
+                updateReferences( repositoryGroup, newCfg );
+                RepositoryGroupConfiguration oldCfg = configuration.findRepositoryGroupById( id );
+                if ( oldCfg != null )
+                {
+                    configuration.removeRepositoryGroup( oldCfg );
                 }
-                configuration.addRepositoryGroup(newCfg);
-                repositoryRegistry.saveConfiguration(configuration);
+                configuration.addRepositoryGroup( newCfg );
+                configurationHandler.save( configuration, ConfigurationHandler.REGISTRY_EVENT_TAG );
                 initializeGroup( repositoryGroup );
-                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()));
             }
+            finally
+            {
+                configLock.unlock( );
+            }
+            repositoryGroups.put( id, repositoryGroup );
+            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( ) ), e);
+        }
     }
 
     /**
@@ -245,72 +307,153 @@ public class RepositoryGroupHandler
      * @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 repositoryGroup = repositoryGroups.get(id);
-            RepositoryGroupConfiguration oldCfg = repositoryGroup != null ? repositoryRegistry.getProvider(repositoryType).getRepositoryGroupConfiguration(repositoryGroup) : null;
-            repositoryGroup = putRepositoryGroup(repositoryGroupConfiguration, configuration);
-            try {
-                repositoryRegistry.saveConfiguration(configuration);
-            } catch ( IndeterminateConfigurationException | RegistryException e) {
-                if (oldCfg != null) {
-                    repositoryRegistry.getProvider(repositoryType).updateRepositoryGroupInstance((EditableRepositoryGroup) repositoryGroup, oldCfg);
+    @Override
+    public RepositoryGroup put( RepositoryGroupConfiguration repositoryGroupConfiguration ) throws RepositoryException
+    {
+        final String id = repositoryGroupConfiguration.getId( );
+        final RepositoryType repositoryType = RepositoryType.valueOf( repositoryGroupConfiguration.getType( ) );
+        final RepositoryProvider provider = repositoryRegistry.getProvider( repositoryType );
+        RepositoryGroup currentRepository;
+        ReentrantReadWriteLock.WriteLock configLock = this.configurationHandler.getLock( ).writeLock( );
+        configLock.lock( );
+        try
+        {
+            Configuration configuration = this.configurationHandler.getBaseConfiguration( );
+            currentRepository = repositoryRegistry.getRepositoryGroup( id );
+            RepositoryGroup oldRepository = currentRepository == null ? null : clone( currentRepository );
+            try
+            {
+
+                if (currentRepository==null) {
+                    currentRepository = put( repositoryGroupConfiguration, configuration );
+                } else
+                {
+                    setRepositoryGroupDefaults( repositoryGroupConfiguration );
+                    provider.updateRepositoryGroupInstance( (EditableRepositoryGroup) currentRepository, repositoryGroupConfiguration );
                 }
-                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());
+                configurationHandler.save( configuration, ConfigurationHandler.REGISTRY_EVENT_TAG );
+                updateReferences( currentRepository, repositoryGroupConfiguration );
+                initializeGroup( currentRepository );
+                this.repositoryGroups.put( id, currentRepository );
             }
-            return repositoryGroup;
+            catch ( IndeterminateConfigurationException | RegistryException | RepositoryException e )
+            {
+                // Trying a rollback
+                if ( oldRepository != null  )
+                {
+                    RepositoryGroupConfiguration oldCfg = provider.getRepositoryGroupConfiguration( oldRepository );
+                    provider.updateRepositoryGroupInstance( (EditableRepositoryGroup) currentRepository, oldCfg);
+                    replaceOrAddRepositoryConfig( oldCfg, configuration );
+                    try
+                    {
+                        configurationHandler.save( configuration, ConfigurationHandler.REGISTRY_EVENT_TAG );
+                    }
+                    catch ( IndeterminateConfigurationException | RegistryException indeterminateConfigurationException )
+                    {
+                        log.error( "Fatal error, config save during rollback failed: {}", e.getMessage( ), e );
+                    }
+                    updateReferences( oldRepository, oldCfg  );
+                    initializeGroup( oldRepository );
+                }
+                log.error( "Could not save the configuration for repository group {}: {}", id, e.getMessage( ), e );
+                if (e instanceof RepositoryException) {
+                    throw (RepositoryException) e;
+                } else
+                {
+                    throw new RepositoryException( "Could not save the configuration for repository group " + id + ": " + e.getMessage( ) );
+                }
+            }
+        }
+        finally
+        {
+            configLock.unlock( );
+        }
+        return currentRepository;
     }
 
-    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);
-                this.mergedRemoteIndexesScheduler.unschedule( repo );
-                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);
+    @Override
+    public RepositoryGroup put( 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 = clone( repositoryGroups.get( id ) );
+            if ( repo instanceof EditableRepositoryGroup )
+            {
+                repositoryRegistry.getProvider( repoType ).updateRepositoryGroupInstance( (EditableRepositoryGroup) repo, repositoryGroupConfiguration );
+            }
+            else
+            {
+                throw new RepositoryException( "The repository is not editable " + id );
             }
-            updateRepositoryReferences(repositoryRegistry.getProvider(repoType), repo, repositoryGroupConfiguration);
-            replaceOrAddRepositoryConfig(repositoryGroupConfiguration, configuration);
-            initializeGroup( repo );
-            return repo;
+        }
+        else
+        {
+            repo = repositoryRegistry.getProvider( repoType ).createRepositoryGroup( repositoryGroupConfiguration );
+        }
+        replaceOrAddRepositoryConfig( repositoryGroupConfiguration, configuration );
+        return repo;
     }
 
-    private void setRepositoryGroupDefaults(RepositoryGroupConfiguration repositoryGroupConfiguration) {
-        if ( StringUtils.isEmpty(repositoryGroupConfiguration.getMergedIndexPath())) {
-            repositoryGroupConfiguration.setMergedIndexPath(DEFAULT_INDEX_PATH);
+    @Override
+    public <D> CheckedResult<RepositoryGroup, D> putWithCheck( RepositoryGroupConfiguration repositoryConfiguration, RepositoryChecker<RepositoryGroup, D> checker ) throws RepositoryException
+    {
+        final String id = repositoryConfiguration.getId( );
+        RepositoryGroup currentGroup = repositoryGroups.get( id );
+        Configuration configuration = configurationHandler.getBaseConfiguration( );
+        RepositoryGroup repositoryGroup = put( repositoryConfiguration, configuration );
+        CheckedResult<RepositoryGroup, D> result;
+        if ( currentGroup == null )
+        {
+            result = checker.apply( repositoryGroup );
         }
-        if (repositoryGroupConfiguration.getMergedIndexTtl() <= 0) {
-            repositoryGroupConfiguration.setMergedIndexTtl(300);
+        else
+        {
+            result = checker.applyForUpdate( repositoryGroup );
         }
-        if (StringUtils.isEmpty(repositoryGroupConfiguration.getCronExpression())) {
-            repositoryGroupConfiguration.setCronExpression("0 0 03 ? * MON");
+        if ( result.isValid( ) )
+        {
+            put( repositoryConfiguration );
         }
+        return result;
     }
 
-    private void replaceOrAddRepositoryConfig(RepositoryGroupConfiguration repositoryGroupConfiguration, Configuration configuration) {
-        RepositoryGroupConfiguration oldCfg = configuration.findRepositoryGroupById(repositoryGroupConfiguration.getId());
-        if (oldCfg != null) {
-            configuration.removeRepositoryGroup(oldCfg);
+
+    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" );
         }
-        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));
+    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 ) );
         }
     }
 
@@ -321,75 +464,122 @@ public class RepositoryGroupHandler
      * @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) {
+    @Override
+    public void remove( final String id ) throws RepositoryException
+    {
+        RepositoryGroup repo = get( id );
+        if ( repo != null )
+        {
+            try
+            {
+                repo = repositoryGroups.remove( id );
+                if ( repo != null )
+                {
                     this.mergedRemoteIndexesScheduler.unschedule( repo );
-                    repo.close();
-                    Configuration configuration = this.configurationHandler.getBaseConfiguration();
-                    RepositoryGroupConfiguration cfg = configuration.findRepositoryGroupById(id);
-                    if (cfg != null) {
-                        configuration.removeRepositoryGroup(cfg);
+                    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 );
+                    this.configurationHandler.save( configuration, ConfigurationHandler.REGISTRY_EVENT_TAG );
                 }
 
-            } catch (RegistryException | IndeterminateConfigurationException e) {
+            }
+            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());
+                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) {
-                    this.mergedRemoteIndexesScheduler.unschedule( repo );
-                    repo.close();
-                    RepositoryGroupConfiguration cfg = configuration.findRepositoryGroupById(id);
-                    if (cfg != null) {
-                        configuration.removeRepositoryGroup(cfg);
-                    }
+    @Override
+    public void remove( String id, Configuration configuration ) throws RepositoryException
+    {
+        RepositoryGroup repo = repositoryGroups.get( id );
+        if ( repo != null )
+        {
+            repo = repositoryGroups.remove( id );
+            if ( repo != null )
+            {
+                this.mergedRemoteIndexesScheduler.unschedule( repo );
+                repo.close( );
+                RepositoryGroupConfiguration cfg = configuration.findRepositoryGroupById( id );
+                if ( cfg != null )
+                {
+                    configuration.removeRepositoryGroup( cfg );
                 }
+            }
         }
 
     }
 
-    public RepositoryGroup getRepositoryGroup( String groupId ) {
-        return repositoryGroups.get(groupId);
+    @Override
+    public RepositoryGroup get( String groupId )
+    {
+        return repositoryGroups.get( groupId );
     }
 
-    public Collection<RepositoryGroup> getRepositoryGroups() {
+    @Override
+    public RepositoryGroup clone( RepositoryGroup repo ) throws RepositoryException
+    {
+        RepositoryProvider provider = repositoryRegistry.getProvider( repo.getType( ) );
+        RepositoryGroupConfiguration cfg = provider.getRepositoryGroupConfiguration( repo );
+        RepositoryGroup cloned = provider.createRepositoryGroup( cfg );
+        cloned.registerEventHandler( RepositoryEvent.ANY, repositoryRegistry );
+        return cloned;
+    }
+
+    @Override
+    public void updateReferences( RepositoryGroup repo, RepositoryGroupConfiguration repositoryConfiguration ) throws RepositoryException
+    {
+        if ( repo instanceof EditableRepositoryGroup )
+        {
+            EditableRepositoryGroup eGroup = (EditableRepositoryGroup) repo;
+            eGroup.setRepositories( repositoryConfiguration.getRepositories( ).stream( )
+                .map( repositoryRegistry::getManagedRepository ).collect( Collectors.toList( ) ) );
+        }
+
+    }
+
+    @Override
+    public Collection<RepositoryGroup> getAll( )
+    {
         return repositoryGroups.values( );
     }
 
-    public boolean hasRepositoryGroup(String id) {
+    @Override
+    public boolean has( String id )
+    {
         return repositoryGroups.containsKey( id );
     }
 
     @PreDestroy
-    private void destroy() {
+    private void destroy( )
+    {
         this.close( );
     }
 
-    public void close() {
-        for (RepositoryGroup group : repositoryGroups.values()) {
+    @Override
+    public void close( )
+    {
+        for ( RepositoryGroup group : repositoryGroups.values( ) )
+        {
             try
             {
                 mergedRemoteIndexesScheduler.unschedule( group );
                 group.close( );
-            } catch (Throwable e) {
+            }
+            catch ( Throwable e )
+            {
                 log.error( "Could not close repository group {}: {}", group.getId( ), e.getMessage( ) );
             }
         }
-        this.repositoryGroups.clear();
+        this.repositoryGroups.clear( );
     }
 
 }
diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/validation/CommonGroupValidator.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/validation/CommonGroupValidator.java
new file mode 100644 (file)
index 0000000..f9fd6fd
--- /dev/null
@@ -0,0 +1,144 @@
+package org.apache.archiva.repository.base.validation;
+/*
+ * 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.RepositoryGroup;
+import org.apache.archiva.repository.RepositoryRegistry;
+import org.apache.archiva.repository.base.ConfigurationHandler;
+import org.apache.archiva.repository.validation.AbstractRepositoryValidator;
+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.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ *
+ * A validator for repository groups. All validation errors are prefixed with category 'repository_group'.
+ *
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+@Service( "repositoryValidator#common#group" )
+public class CommonGroupValidator extends AbstractRepositoryValidator<RepositoryGroup> implements RepositoryValidator<RepositoryGroup>
+{
+
+    private static final Pattern REPO_GROUP_ID_PATTERN = Pattern.compile( "[A-Za-z0-9\\._\\-]+" );
+    private final ConfigurationHandler configurationHandler;
+
+    private RepositoryRegistry repositoryRegistry;
+
+    public CommonGroupValidator( ConfigurationHandler configurationHandler )
+    {
+        this.configurationHandler = configurationHandler;
+    }
+
+
+    private Map<String, List<ValidationError>> appendError( Map<String, List<ValidationError>> errorMap, String errorKey, Object... parameter )
+    {
+        Map<String, List<ValidationError>> result;
+        result = errorMap == null ? new HashMap<>( ) : errorMap;
+        ValidationError error = ValidationError.ofKey( errorKey, parameter );
+        List<ValidationError> errList = result.computeIfAbsent( error.getAttribute( ), k -> new ArrayList<ValidationError>( ) );
+        errList.add( error );
+        return result;
+    }
+
+    public ValidationResponse apply( RepositoryGroup repositoryGroup, boolean updateMode ) throws IllegalArgumentException
+    {
+        final String repoGroupId = repositoryGroup.getId( );
+        Map<String, List<ValidationError>> errors = null;
+        if ( StringUtils.isBlank( repoGroupId ) )
+        {
+            errors = appendError( errors, "repository_group.id.empty" );
+        }
+
+        if ( repoGroupId.length( ) > 100 )
+        {
+            errors = appendError( errors, "repository_group.id.max_length", repoGroupId, Integer.toString( 100 ) );
+
+        }
+
+        Matcher matcher = REPO_GROUP_ID_PATTERN.matcher( repoGroupId );
+        if ( !matcher.matches( ) )
+        {
+            errors = appendError( errors, "repository_group.id.invalid_chars", "alphanumeric, '.', '-','_'" );
+        }
+
+        if ( repositoryGroup.getMergedIndexTTL( ) <= 0 )
+        {
+            errors = appendError( errors, "repository_group.merged_index_ttl.min", "0" );
+        }
+
+
+        if ( repositoryRegistry != null && !updateMode )
+        {
+            if ( repositoryRegistry.hasRepositoryGroup( repoGroupId ) )
+            {
+                errors = appendError( errors, "repository_group.id.group_exists", repoGroupId );
+            }
+            else if ( repositoryRegistry.hasManagedRepository( repoGroupId ) )
+            {
+                errors = appendError( errors, "repository_group.id.managed_exists" );
+            }
+            else if ( repositoryRegistry.hasRemoteRepository( repoGroupId ) )
+            {
+                errors = appendError( errors, "repository_group.id.remote_exists" );
+            }
+        }
+        return new ValidationResponse(repositoryGroup, errors );
+    }
+
+
+
+
+    public ConfigurationHandler getConfigurationHandler( )
+    {
+        return configurationHandler;
+    }
+
+    public RepositoryRegistry getRepositoryRegistry( )
+    {
+        return repositoryRegistry;
+    }
+
+    @Override
+    public void setRepositoryRegistry( RepositoryRegistry repositoryRegistry )
+    {
+        this.repositoryRegistry = repositoryRegistry;
+    }
+
+    @Override
+    public Class<RepositoryGroup> getFlavour( )
+    {
+        return RepositoryGroup.class;
+    }
+
+    @Override
+    public boolean isFlavour( Class<?> clazz )
+    {
+        return RepositoryGroup.class.isAssignableFrom( clazz );
+    }
+}
index b7cbf1fb572de3050ac6be4a7fa85fe1eb5860d2..00bbbc8c1130e12a79995bec7552ebe4fe805e82 100644 (file)
@@ -30,15 +30,14 @@ 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.test.utils.ArchivaSpringJUnit4ClassRunner;
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+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;
@@ -50,12 +49,13 @@ import java.nio.file.Paths;
 import java.nio.file.StandardCopyOption;
 import java.util.Collection;
 
-import static org.junit.Assert.*;
+import static org.junit.jupiter.api.Assertions.*;
+
 
 /**
  * Test for RepositoryRegistry
  */
-@RunWith(ArchivaSpringJUnit4ClassRunner.class)
+@ExtendWith( SpringExtension.class)
 @ContextConfiguration(locations = { "classpath*:/META-INF/spring-context.xml", "classpath:/spring-context.xml" })
 public class ArchivaRepositoryRegistryTest
 {
@@ -75,7 +75,7 @@ public class ArchivaRepositoryRegistryTest
     private static Path cfgCopy;
     private static Path archivaCfg;
 
-    @BeforeClass
+    @BeforeAll
     public static void classSetup() throws IOException, URISyntaxException
     {
         URL archivaCfgUri = Thread.currentThread().getContextClassLoader().getResource( "archiva.xml" );
@@ -86,7 +86,7 @@ public class ArchivaRepositoryRegistryTest
         }
     }
 
-    @AfterClass
+    @AfterAll
     public static void classTearDown() throws IOException
     {
         if (cfgCopy!=null) {
@@ -94,7 +94,7 @@ public class ArchivaRepositoryRegistryTest
         }
     }
 
-    @Before
+    @BeforeEach
     public void setUp( ) throws Exception
     {
         assertNotNull( repositoryRegistry );
@@ -111,7 +111,7 @@ public class ArchivaRepositoryRegistryTest
         repositoryRegistry.reload();
     }
 
-    @After
+    @AfterEach
     public void tearDown( ) throws Exception
     {
         Files.deleteIfExists( userCfg );
@@ -160,7 +160,7 @@ public class ArchivaRepositoryRegistryTest
         assertTrue(repo instanceof ManagedRepository);
         assertTrue( repo.hasIndex( ) );
         assertTrue(repo.isScanned());
-        Assert.assertEquals( RepositoryType.MAVEN, repo.getType());
+        assertEquals( RepositoryType.MAVEN, repo.getType());
     }
 
     @Test
@@ -213,7 +213,7 @@ public class ArchivaRepositoryRegistryTest
 
         managedRepository = BasicManagedRepository.newFilesystemInstance("central", "Test repo", archivaConfiguration.getRepositoryBaseDir().resolve("central"));
         managedRepository.setDescription( managedRepository.getPrimaryLocale(), "This is just a test" );
-        ManagedRepository updatedRepo = null;
+        ManagedRepository updatedRepo;
         try {
             repositoryRegistry.putRepository( managedRepository );
             throw new RuntimeException("Repository exception should be thrown, if there exists a remote repository already with that id");
@@ -224,12 +224,12 @@ public class ArchivaRepositoryRegistryTest
         managedRepository.setDescription( managedRepository.getPrimaryLocale(), "This is just a test" );
         updatedRepo = repositoryRegistry.putRepository( managedRepository );
 
-        assertTrue(updatedRepo==managedRepository);
+        assertSame( updatedRepo, managedRepository );
         assertNotNull(managedRepository.getContent());
         assertEquals(6, repositoryRegistry.getRepositories().size());
         ManagedRepository managedRepository1 = repositoryRegistry.getManagedRepository( "internal" );
         assertEquals("Test repo", managedRepository1.getName());
-        assertTrue(managedRepository1==managedRepository);
+        assertSame( managedRepository1, managedRepository );
 
     }
 
@@ -253,7 +253,7 @@ public class ArchivaRepositoryRegistryTest
         cfg.setId("internal");
         cfg.setName("This is internal test 002");
         repo = repositoryRegistry.putRepository( cfg );
-        assertTrue(internalRepo==repo);
+        assertSame( internalRepo, repo );
         assertEquals("This is internal test 002",repo.getName());
         assertEquals(5, repositoryRegistry.getManagedRepositories().size());
 
@@ -284,7 +284,7 @@ public class ArchivaRepositoryRegistryTest
         cfg.setId("internal");
         cfg.setName("This is internal test 002");
         repo = repositoryRegistry.putRepository( cfg, configuration );
-        assertTrue(internalRepo==repo);
+        assertSame( internalRepo, repo );
         assertEquals("This is internal test 002",repo.getName());
         assertEquals(5, repositoryRegistry.getManagedRepositories().size());
 
@@ -299,16 +299,17 @@ public class ArchivaRepositoryRegistryTest
         remoteRepository.setDescription( remoteRepository.getPrimaryLocale(), "This is just a test" );
         RemoteRepository newRepo = repositoryRegistry.putRepository(remoteRepository);
 
-        assertTrue(remoteRepository==newRepo);
+        assertSame( remoteRepository, newRepo );
         assertNotNull(remoteRepository.getContent());
         assertEquals(6, repositoryRegistry.getRepositories().size());
 
         remoteRepository = BasicRemoteRepository.newFilesystemInstance( "internal", "Test repo", archivaConfiguration.getRemoteRepositoryBaseDir() );
         remoteRepository.setDescription( remoteRepository.getPrimaryLocale(), "This is just a test" );
-        RemoteRepository updatedRepo = null;
+        RemoteRepository updatedRepo;
         try
         {
             updatedRepo = repositoryRegistry.putRepository( remoteRepository );
+            assertSame( remoteRepository, updatedRepo );
             throw new RuntimeException("Should throw repository exception, if repository exists already and is not the same type.");
         } catch (RepositoryException e) {
             // OK
@@ -318,12 +319,12 @@ public class ArchivaRepositoryRegistryTest
         remoteRepository.setDescription( remoteRepository.getPrimaryLocale(), "This is just a test" );
         updatedRepo = repositoryRegistry.putRepository( remoteRepository );
 
-        assertTrue(updatedRepo==remoteRepository);
+        assertSame( updatedRepo, remoteRepository );
         assertNotNull(remoteRepository.getContent());
         assertEquals(6, repositoryRegistry.getRepositories().size());
         RemoteRepository remoteRepository1 = repositoryRegistry.getRemoteRepository( "central" );
         assertEquals("Test repo", remoteRepository1.getName());
-        assertTrue(remoteRepository1==remoteRepository);
+        assertSame( remoteRepository1, remoteRepository );
     }
 
     @Test
@@ -346,7 +347,7 @@ public class ArchivaRepositoryRegistryTest
         cfg.setId("central");
         cfg.setName("This is central test 002");
         repo = repositoryRegistry.putRepository( cfg );
-        assertTrue(internalRepo==repo);
+        assertSame( internalRepo, repo );
         assertEquals("This is central test 002",repo.getName());
         assertEquals(2, repositoryRegistry.getRemoteRepositories().size());
 
@@ -376,7 +377,7 @@ public class ArchivaRepositoryRegistryTest
         cfg.setId("central");
         cfg.setName("This is central test 002");
         repo = repositoryRegistry.putRepository( cfg, configuration );
-        assertTrue(internalRepo==repo);
+        assertSame( internalRepo, repo );
         assertEquals("This is central test 002",repo.getName());
         assertEquals(2, repositoryRegistry.getRemoteRepositories().size());
 
@@ -483,7 +484,7 @@ public class ArchivaRepositoryRegistryTest
         assertNotNull(clone);
         assertNull(clone.getContent());
         assertEquals("Archiva Managed Internal Repository", clone.getName());
-        assertFalse(managedRepository==clone);
+        assertNotSame( managedRepository, clone );
 
     }
 
@@ -516,8 +517,15 @@ public class ArchivaRepositoryRegistryTest
         assertNotNull(clone);
         assertNull(clone.getContent());
         assertEquals("Central Repository", clone.getName());
-        assertFalse(remoteRepository==clone);
+        assertNotSame( remoteRepository, clone );
+
+    }
 
+    @Test
+    void validateRepository() {
+        Repository repo = repositoryRegistry.getRepository( "internal" );
+        assertNotNull( repo );
+        assertTrue( repositoryRegistry.validateRepository( repo ).isValid() );
     }
 
 }
\ No newline at end of file
index 2afc249bb0474768fbfe6637d82c10bde41c248b..1deadfe38ddc7b70f0a381c431b10ce020f38af1 100644 (file)
@@ -27,8 +27,6 @@ import org.apache.archiva.configuration.RemoteRepositoryConfiguration;
 import org.apache.archiva.configuration.RepositoryGroupConfiguration;
 import org.apache.archiva.event.Event;
 import org.apache.archiva.event.EventHandler;
-import org.apache.archiva.event.EventManager;
-import org.apache.archiva.event.EventType;
 import org.apache.archiva.repository.EditableManagedRepository;
 import org.apache.archiva.repository.EditableRemoteRepository;
 import org.apache.archiva.repository.EditableRepository;
@@ -76,7 +74,7 @@ import static org.apache.archiva.indexer.ArchivaIndexManager.DEFAULT_INDEX_PATH;
 import static org.apache.archiva.indexer.ArchivaIndexManager.DEFAULT_PACKED_INDEX_PATH;
 
 /**
- * Provider for the maven2 repository implementations
+ * Provider for the maven2 repository implementation
  */
 @Service("mavenRepositoryProvider")
 public class MavenRepositoryProvider implements RepositoryProvider {
@@ -88,28 +86,7 @@ public class MavenRepositoryProvider implements RepositoryProvider {
     @Inject
     private FileLockManager fileLockManager;
 
-    private class EventHandlerInfo {
-        EventType<?> type;
-        EventHandler<?> handler;
-
-        public EventHandlerInfo( EventType<?> type, EventHandler<?> handler )
-        {
-            this.type = type;
-            this.handler = handler;
-        }
-
-        public EventType<?> getType( )
-        {
-            return type;
-        }
-
-        public EventHandler<?> getHandler( )
-        {
-            return handler;
-        }
-    }
-
-    private List<EventHandler<? super RepositoryEvent>> repositoryEventHandlers = new ArrayList<>( );
+    private final List<EventHandler<? super RepositoryEvent>> repositoryEventHandlers = new ArrayList<>( );
 
     private static final Logger log = LoggerFactory.getLogger(MavenRepositoryProvider.class);
 
@@ -152,7 +129,7 @@ public class MavenRepositoryProvider implements RepositoryProvider {
         try {
             storage = new FilesystemStorage(baseDir.resolve(id), fileLockManager);
         } catch (IOException e) {
-            log.error("Could not initialize fileystem for repository {}", id);
+            log.error("Could not initialize filesystem for repository {}: '{}'", id, e.getMessage());
             throw new RuntimeException(e);
         }
         MavenRemoteRepository repo = new MavenRemoteRepository( id, name, storage );
@@ -162,15 +139,15 @@ public class MavenRepositoryProvider implements RepositoryProvider {
 
     @Override
     public EditableRepositoryGroup createRepositoryGroup(String id, String name) {
-        return createRepositoryGroup(id, name, archivaConfiguration.getRepositoryBaseDir());
+        return createRepositoryGroup(id, name, archivaConfiguration.getRepositoryGroupBaseDir().resolve( id ));
     }
 
-    public MavenRepositoryGroup createRepositoryGroup(String id, String name, Path baseDir) {
+    public MavenRepositoryGroup createRepositoryGroup(String id, String name, Path repositoryPath) {
         FilesystemStorage storage;
         try {
-            storage = new FilesystemStorage(baseDir.resolve(id), fileLockManager);
+            storage = new FilesystemStorage(repositoryPath, fileLockManager);
         } catch (IOException e) {
-            log.error("Could not initialize fileystem for repository {}", id);
+            log.error("Could not initialize filesystem for repository {}: '{}'", id, e.getMessage());
             throw new RuntimeException(e);
         }
         MavenRepositoryGroup group = new MavenRepositoryGroup( id, name, storage );
@@ -349,13 +326,21 @@ public class MavenRepositoryProvider implements RepositoryProvider {
 
     @Override
     public RepositoryGroup createRepositoryGroup(RepositoryGroupConfiguration configuration) throws RepositoryException {
-        Path repositoryGroupBase = getArchivaConfiguration().getRepositoryGroupBaseDir();
+        Path repositoryPath = getRepositoryGroupPath( configuration );
         MavenRepositoryGroup newGrp = createRepositoryGroup(configuration.getId(), configuration.getName(),
-                repositoryGroupBase);
+                repositoryPath);
         updateRepositoryGroupInstance(newGrp, configuration);
         return newGrp;
     }
 
+    private Path getRepositoryGroupPath(RepositoryGroupConfiguration configuration) {
+        if (StringUtils.isNotEmpty( configuration.getLocation() )) {
+            return Paths.get( configuration.getLocation( ) );
+        } else {
+            return getArchivaConfiguration( ).getRepositoryGroupBaseDir( ).resolve( configuration.getId( ) );
+        }
+    }
+
     @Override
     public void updateRepositoryGroupInstance(EditableRepositoryGroup repositoryGroup, RepositoryGroupConfiguration configuration) throws RepositoryException {
         repositoryGroup.setName(repositoryGroup.getPrimaryLocale(), configuration.getName());
@@ -551,7 +536,8 @@ public class MavenRepositoryProvider implements RepositoryProvider {
         return stagingRepository;
     }
 
-    private void setBaseConfig(EditableRepository repo, AbstractRepositoryConfiguration cfg) throws RepositoryException {
+    private void setBaseConfig( EditableRepository repo, AbstractRepositoryConfiguration cfg)
+    {
 
         URI baseUri = archivaConfiguration.getRepositoryBaseDir().toUri();
         repo.setBaseUri(baseUri);
index 4ceed3865f8e9b4503561baf179d78ff4240d48d..c9fba72a6396ce025b7ece8cf35fa80e71e0500f 100644 (file)
@@ -23,7 +23,9 @@ 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 org.apache.archiva.repository.validation.RepositoryValidator;
 
+import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
 
@@ -32,9 +34,9 @@ public class RepositoryRegistryMock extends ArchivaRepositoryRegistry
 
     private Map<String, ManagedRepository> managedRepositories = new TreeMap<>();
 
-    public RepositoryRegistryMock( ConfigurationHandler configurationHandler )
+    public RepositoryRegistryMock( ConfigurationHandler configurationHandler, List<RepositoryValidator<? extends Repository>> validatorList )
     {
-        super( configurationHandler );
+        super( configurationHandler, validatorList );
     }
 
     @Override
index 7eea247021200425614f30470cf6a0258e742a9e..1f021d0f448392dd048aece407081188a4d2fa50 100644 (file)
@@ -35,12 +35,16 @@ package org.apache.archiva.rest.api.model.v2;/*
  */
 
 import io.swagger.v3.oas.annotations.media.Schema;
+import org.apache.archiva.repository.Repository;
+import org.apache.archiva.repository.features.IndexCreationFeature;
+import org.apache.archiva.repository.features.RepositoryFeature;
 
 import javax.xml.bind.annotation.XmlRootElement;
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.stream.Collectors;
 
 /**
  * @author Martin Stockhammer <martin_s@apache.org>
@@ -63,16 +67,19 @@ public class RepositoryGroup implements Serializable
         this.id = id;
     }
 
-    public static RepositoryGroup of( org.apache.archiva.admin.model.beans.RepositoryGroup modelObj ) {
+    public static RepositoryGroup of( org.apache.archiva.repository.RepositoryGroup modelObj ) {
         RepositoryGroup result = new RepositoryGroup( );
         MergeConfiguration mergeConfig = new MergeConfiguration( );
         result.setMergeConfiguration( mergeConfig );
         result.setId( modelObj.getId() );
-        result.setLocation( modelObj.getLocation() );
-        result.setRepositories( modelObj.getRepositories() );
-        mergeConfig.setMergedIndexPath( modelObj.getMergedIndexPath() );
-        mergeConfig.setMergedIndexTtlMinutes( modelObj.getMergedIndexTtl( ) );
-        mergeConfig.setIndexMergeSchedule( modelObj.getCronExpression( ) );
+        result.setLocation( modelObj.getLocation().toString() );
+        result.setRepositories( modelObj.getRepositories().stream().map( Repository::getId ).collect( Collectors.toList()) );
+        if (modelObj.supportsFeature( IndexCreationFeature.class )) {
+            IndexCreationFeature icf = modelObj.getFeature( IndexCreationFeature.class ).get();
+            mergeConfig.setMergedIndexPath( icf.getIndexPath( ).toString() );
+            mergeConfig.setMergedIndexTtlMinutes( modelObj.getMergedIndexTTL( ) );
+            mergeConfig.setIndexMergeSchedule( modelObj.getSchedulingDefinition() );
+        }
         return result;
     }
 
diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/ValidationError.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/ValidationError.java
new file mode 100644 (file)
index 0000000..efa1ec9
--- /dev/null
@@ -0,0 +1,139 @@
+package org.apache.archiva.rest.api.model.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.swagger.v3.oas.annotations.media.Schema;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+@XmlRootElement(name = "validationError")
+@Schema(name = "ValidationError", description = "A validation error.")
+public class ValidationError implements Serializable
+{
+    private static final long serialVersionUID = 2079020598090660171L;
+
+    String key;
+    String field;
+    String category;
+    String type;
+    List<String> parameter;
+
+
+    public ValidationError( )
+    {
+    }
+
+    public ValidationError( String key, String field, String category, String type, List<String> parameter) {
+        this.key = key;
+        this.field = field;
+        this.category = category;
+        this.type = type;
+        if (parameter==null) {
+            this.parameter = new ArrayList<>( );
+        } else
+        {
+            this.parameter = parameter;
+        }
+    }
+
+    /**
+     * Creates a new instance based on the given error
+     * @param error the error instance
+     * @return
+     */
+    public static ValidationError of( org.apache.archiva.repository.validation.ValidationError error ) {
+        return error != null ? new ValidationError( error.getErrorKey( ), error.getAttribute( ), error.getCategory( ),
+            error.getType( ), error.getArguments( ).stream( ).map( Object::toString ).collect( Collectors.toList( ) ) )
+            : new ValidationError( );
+    }
+
+    /**
+     * Creates a new instance based on the field name and the error instance
+     * @param fieldName the name of the field to which the error applies
+     * @param error the error definition
+     * @return a new validation error instance
+     */
+    public static ValidationError of( String fieldName, org.apache.archiva.repository.validation.ValidationError error ) {
+        return error != null ? new ValidationError( error.getErrorKey( ), fieldName, error.getCategory( ),
+            error.getType( ), error.getArguments( ).stream( ).map( Object::toString ).collect( Collectors.toList( ) ) )
+            : new ValidationError( );
+    }
+
+    @Schema(name="key", description = "The full key of the validation error")
+    public String getKey( )
+    {
+        return key;
+    }
+
+    public void setKey( String key )
+    {
+        this.key = key;
+    }
+
+    @Schema(name="field", description = "The name of the field where the error was detected")
+    public String getField( )
+    {
+        return field;
+    }
+
+    public void setField( String field )
+    {
+        this.field = field;
+    }
+
+    @Schema(name="category", description = "The name of the category this error is assigned to")
+    public String getCategory( )
+    {
+        return category;
+    }
+
+    public void setCategory( String category )
+    {
+        this.category = category;
+    }
+
+    @Schema(name="type", description = "The type of the error. This is a unique string that defines the type of error, e.g. empty, bad_number_range, ... .")
+    public String getType( )
+    {
+        return type;
+    }
+
+    public void setType( String type )
+    {
+        this.type = type;
+    }
+
+    @Schema(name="parameter", description = "The list of parameters, that can be used to create a translated error message")
+    public List<String> getParameter( )
+    {
+        return parameter;
+    }
+
+    public void setParameter( List<String> parameter )
+    {
+        this.parameter = parameter;
+    }
+}
index 0df9475050a7b1f336d61eb7b012cffcab461da0..17c5b89d2d7345996e9a357b193889fa9b72a59e 100644 (file)
@@ -19,11 +19,13 @@ package org.apache.archiva.rest.api.services.v2;
  */
 
 import io.swagger.v3.oas.annotations.media.Schema;
+import org.apache.archiva.rest.api.model.v2.ValidationError;
 import org.apache.commons.lang3.StringUtils;
 
 import javax.xml.bind.annotation.XmlRootElement;
 import java.io.Serializable;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -37,7 +39,8 @@ public class ArchivaRestError
 {
 
     private static final long serialVersionUID = -8892617571273167067L;
-    private List<ErrorMessage> errorMessages = new ArrayList<ErrorMessage>( 1 );
+    private List<ErrorMessage> errorMessages = new ArrayList<>( 1 );
+    private List<ValidationError> validationErrors;
 
     public ArchivaRestError()
     {
@@ -51,6 +54,19 @@ public class ArchivaRestError
         {
             errorMessages.add( new ErrorMessage( e.getMessage(), null ) );
         }
+        if (e instanceof ValidationException) {
+            this.validationErrors = ( (ValidationException) e ).getValidationErrors( );
+        }
+    }
+
+    public ArchivaRestError( ValidationException e )
+    {
+        errorMessages.addAll( e.getErrorMessages() );
+        if ( e.getErrorMessages().isEmpty() && StringUtils.isNotEmpty( e.getMessage() ) )
+        {
+            errorMessages.add( new ErrorMessage( e.getMessage(), null ) );
+        }
+        this.validationErrors = e.getValidationErrors( );
     }
 
     @Schema(name="error_messages", description = "The list of errors that occurred while processing the REST request")
@@ -68,4 +84,15 @@ public class ArchivaRestError
     {
         this.errorMessages.add( errorMessage );
     }
+
+    @Schema( name = "has_validation_errors", description = "True, if the error contains validation errors" )
+    public boolean hasValidationErrors( )
+    {
+        return this.validationErrors != null && this.validationErrors.size( ) > 0;
+    }
+
+    @Schema( name = "validation_errors", description = "The list of validation errors")
+    public List<ValidationError> getValidationErrors() {
+        return hasValidationErrors() ? this.validationErrors : Collections.emptyList( );
+    }
 }
index ab74ceaecc7c46bf077b63d72a55eeefb04bf9fc..fee06c9e7b2c0811a4d4e76b9099209079a5121e 100644 (file)
@@ -45,6 +45,9 @@ public interface ErrorKeys
 {
 
     String PREFIX = "archiva.";
+
+    String VALIDATION_ERROR = PREFIX + "validation_error";
+
     String REPOSITORY_GROUP_PREFIX = PREFIX + "repository_group.";
     String REPOSITORY_PREFIX = PREFIX + "repository.";
 
@@ -67,6 +70,7 @@ public interface ErrorKeys
     String REPOSITORY_GROUP_NOT_FOUND = REPOSITORY_GROUP_PREFIX+"notfound";
     String REPOSITORY_GROUP_ADD_FAILED = REPOSITORY_GROUP_PREFIX+"add.failed"  ;
     String REPOSITORY_GROUP_EXIST = REPOSITORY_GROUP_PREFIX+"exists";
+    String REPOSITORY_GROUP_UPDATE_FAILED = REPOSITORY_GROUP_PREFIX + "update.failed";
 
     String REPOSITORY_GROUP_DELETE_FAILED = REPOSITORY_GROUP_PREFIX + "delete.failed";
     String REPOSITORY_NOT_FOUND = REPOSITORY_PREFIX + "notfound";
index a95c68f959d565676c534ef180c579b4c4349bbc..1c0b0595ee2dd560f4b7fb97cdd296c89f48c13d 100644 (file)
@@ -91,7 +91,7 @@ public interface RepositoryGroupService
                                                         @QueryParam( "order" ) @DefaultValue( "asc" ) String order )
         throws ArchivaRestServiceException;
 
-    @Path( "{repositoryGroupId}" )
+    @Path( "{id}" )
     @GET
     @Produces( {APPLICATION_JSON} )
     @RedbackAuthorization( permissions = ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION )
@@ -112,7 +112,7 @@ public interface RepositoryGroupService
                 content = @Content( mediaType = APPLICATION_JSON, schema = @Schema( implementation = ArchivaRestError.class ) ) )
         }
     )
-    RepositoryGroup getRepositoryGroup( @PathParam( "repositoryGroupId" ) String repositoryGroupId )
+    RepositoryGroup getRepositoryGroup( @PathParam( "id" ) String repositoryGroupId )
         throws ArchivaRestServiceException;
 
     @Path( "" )
@@ -133,7 +133,7 @@ public interface RepositoryGroupService
         },
         responses = {
             @ApiResponse( responseCode = "201",
-                description = "If the list could be returned",
+                description = "If the repository group was created",
                 content = @Content( mediaType = APPLICATION_JSON, schema = @Schema( implementation = RepositoryGroup.class ) )
             ),
             @ApiResponse( responseCode = "303", description = "The repository group exists already",
@@ -150,7 +150,7 @@ public interface RepositoryGroupService
     RepositoryGroup addRepositoryGroup( RepositoryGroup repositoryGroup )
         throws ArchivaRestServiceException;
 
-    @Path( "{repositoryGroupId}" )
+    @Path( "{id}" )
     @PUT
     @Consumes( {APPLICATION_JSON} )
     @Produces( {APPLICATION_JSON} )
@@ -179,10 +179,10 @@ public interface RepositoryGroupService
                 content = @Content( mediaType = APPLICATION_JSON, schema = @Schema( implementation = ArchivaRestError.class ) ) )
         }
     )
-    RepositoryGroup updateRepositoryGroup( @PathParam( "repositoryGroupId" ) String groupId, RepositoryGroup repositoryGroup )
+    RepositoryGroup updateRepositoryGroup( @PathParam( "id" ) String groupId, RepositoryGroup repositoryGroup )
         throws ArchivaRestServiceException;
 
-    @Path( "{repositoryGroupId}" )
+    @Path( "{id}" )
     @DELETE
     @Produces( {APPLICATION_JSON} )
     @RedbackAuthorization( permissions = ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION )
@@ -202,10 +202,10 @@ public interface RepositoryGroupService
                 content = @Content( mediaType = APPLICATION_JSON, schema = @Schema( implementation = ArchivaRestError.class ) ) ),
         }
     )
-    Response deleteRepositoryGroup( @PathParam( "repositoryGroupId" ) String repositoryGroupId )
+    Response deleteRepositoryGroup( @PathParam( "id" ) String repositoryGroupId )
         throws ArchivaRestServiceException;
 
-    @Path( "{repositoryGroupId}/repositories/{repositoryId}" )
+    @Path( "{id}/repositories/{repositoryId}" )
     @PUT
     @Produces( {APPLICATION_JSON} )
     @RedbackAuthorization( permissions = ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION )
@@ -225,11 +225,11 @@ public interface RepositoryGroupService
                 content = @Content( mediaType = APPLICATION_JSON, schema = @Schema( implementation = ArchivaRestError.class ) ) ),
         }
     )
-    RepositoryGroup addRepositoryToGroup( @PathParam( "repositoryGroupId" ) String repositoryGroupId,
+    RepositoryGroup addRepositoryToGroup( @PathParam( "id" ) String repositoryGroupId,
                                           @PathParam( "repositoryId" ) String repositoryId )
         throws ArchivaRestServiceException;
 
-    @Path( "{repositoryGroupId}/repositories/{repositoryId}" )
+    @Path( "{id}/repositories/{repositoryId}" )
     @DELETE
     @Produces( {APPLICATION_JSON} )
     @RedbackAuthorization( permissions = ArchivaRoleConstants.OPERATION_MANAGE_CONFIGURATION )
@@ -249,7 +249,7 @@ public interface RepositoryGroupService
                 content = @Content( mediaType = APPLICATION_JSON, schema = @Schema( implementation = ArchivaRestError.class ) ) ),
         }
     )
-    RepositoryGroup deleteRepositoryFromGroup( @PathParam( "repositoryGroupId" ) String repositoryGroupId,
+    RepositoryGroup deleteRepositoryFromGroup( @PathParam( "id" ) String repositoryGroupId,
                                                @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/ValidationException.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/ValidationException.java
new file mode 100644 (file)
index 0000000..bedb46d
--- /dev/null
@@ -0,0 +1,85 @@
+package org.apache.archiva.rest.api.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 org.apache.archiva.repository.Repository;
+import org.apache.archiva.repository.validation.ValidationResponse;
+import org.apache.archiva.rest.api.model.v2.ValidationError;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * Exception is thrown
+ * @author Martin Stockhammer <martin_s@apache.org>
+ */
+public class ValidationException extends ArchivaRestServiceException
+{
+    public static final int DEFAULT_CODE = 422;
+
+    public static final ErrorMessage DEFAULT_MESSAGE = new ErrorMessage( ErrorKeys.VALIDATION_ERROR );
+
+    private List<ValidationError> validationErrors;
+
+    public ValidationException( ) {
+        super( DEFAULT_MESSAGE, DEFAULT_CODE );
+    }
+
+    public ValidationException( int errorCode) {
+        super( DEFAULT_MESSAGE, errorCode );
+    }
+
+    public ValidationException( List<ValidationError> errors) {
+        super( DEFAULT_MESSAGE, DEFAULT_CODE );
+        this.validationErrors = errors;
+    }
+
+    public static ValidationException of( List<org.apache.archiva.repository.validation.ValidationError> errorList ) {
+        return new ValidationException( errorList.stream( ).map( ValidationError::of ).collect( Collectors.toList( ) ) );
+    }
+
+    public static <R extends Repository> ValidationException of( ValidationResponse<R> result ) {
+        if (result.isValid()) {
+            return new ValidationException( );
+        } else
+        {
+            return new ValidationException( result.getResult( ).entrySet( ).stream( ).flatMap(
+                v -> v.getValue( ).stream( ).map( e -> ValidationError.of( v.getKey( ), e ) ) ).collect( Collectors.toList( ) ) );
+        }
+    }
+
+    public List<ValidationError> getValidationErrors( )
+    {
+        return validationErrors==null? Collections.emptyList() : validationErrors;
+    }
+
+    public void setValidationErrors( List<ValidationError> validationErrors )
+    {
+        this.validationErrors = validationErrors;
+    }
+
+    public void addValidationError( ValidationError error) {
+        if (this.validationErrors==null) {
+            this.validationErrors = new ArrayList<>( );
+        }
+        this.validationErrors.add( error );
+    }
+}
index 44ce638acba36755855d661e2f2f383cba3b6350..4535336d67857b9007e5894f9add09a489af7912 100644 (file)
@@ -34,17 +34,23 @@ package org.apache.archiva.rest.services.v2;/*
  * under the License.
  */
 
-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.registry.RegistryException;
 import org.apache.archiva.components.rest.model.PagedResult;
 import org.apache.archiva.components.rest.util.QueryHelper;
+import org.apache.archiva.configuration.Configuration;
+import org.apache.archiva.configuration.IndeterminateConfigurationException;
+import org.apache.archiva.configuration.RepositoryGroupConfiguration;
+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.ValidationResponse;
 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;
 import org.apache.archiva.rest.api.services.v2.ErrorMessage;
 import org.apache.archiva.rest.api.services.v2.RepositoryGroupService;
+import org.apache.archiva.rest.api.services.v2.ValidationException;
 import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -54,14 +60,11 @@ import javax.servlet.http.HttpServletResponse;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
-import java.util.ArrayList;
 import java.util.Comparator;
 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.
  *
@@ -72,25 +75,30 @@ import static org.apache.archiva.rest.services.utils.AuditHelper.getAuditData;
 @Service("v2.repositoryGroupService#rest")
 public class DefaultRepositoryGroupService implements RepositoryGroupService
 {
+    private final ConfigurationHandler configurationHandler;
+
     @Context
     HttpServletResponse httpServletResponse;
 
     @Context
     UriInfo uriInfo;
 
-    final private RepositoryGroupAdmin repositoryGroupAdmin;
+    final private RepositoryRegistry repositoryRegistry;
+
+
 
     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"} );
+    private static final QueryHelper<org.apache.archiva.repository.RepositoryGroup> QUERY_HELPER = new QueryHelper<>( new String[]{"id"} );
     static
     {
-        QUERY_HELPER.addStringFilter( "id", org.apache.archiva.admin.model.beans.RepositoryGroup::getId );
-        QUERY_HELPER.addNullsafeFieldComparator( "id", org.apache.archiva.admin.model.beans.RepositoryGroup::getId );
+        QUERY_HELPER.addStringFilter( "id", org.apache.archiva.repository.RepositoryGroup::getId );
+        QUERY_HELPER.addNullsafeFieldComparator( "id", org.apache.archiva.repository.RepositoryGroup::getId );
     }
 
 
-    public DefaultRepositoryGroupService(RepositoryGroupAdmin repositoryGroupAdmin) {
-        this.repositoryGroupAdmin = repositoryGroupAdmin;
+    public DefaultRepositoryGroupService( RepositoryRegistry repositoryRegistry, ConfigurationHandler configurationHandler ) {
+        this.repositoryRegistry = repositoryRegistry;
+        this.configurationHandler = configurationHandler;
     }
 
     @Override
@@ -98,19 +106,14 @@ public class DefaultRepositoryGroupService implements RepositoryGroupService
     {
         try
         {
-            Predicate<org.apache.archiva.admin.model.beans.RepositoryGroup> filter = QUERY_HELPER.getQueryFilter( searchTerm );
-            Comparator<org.apache.archiva.admin.model.beans.RepositoryGroup> ordering = QUERY_HELPER.getComparator( orderBy, QUERY_HELPER.isAscending( order ) );
-            int totalCount = Math.toIntExact( repositoryGroupAdmin.getRepositoriesGroups( ).stream( ).filter( filter ).count( ) );
-            List<RepositoryGroup> result = repositoryGroupAdmin.getRepositoriesGroups( ).stream( ).filter( filter ).sorted( ordering ).skip( offset ).limit( limit ).map(
+            Predicate<org.apache.archiva.repository.RepositoryGroup> filter = QUERY_HELPER.getQueryFilter( searchTerm );
+            Comparator<org.apache.archiva.repository.RepositoryGroup> ordering = QUERY_HELPER.getComparator( orderBy, QUERY_HELPER.isAscending( order ) );
+            int totalCount = Math.toIntExact( repositoryRegistry.getRepositoryGroups( ).stream( ).filter( filter ).count( ) );
+            List<RepositoryGroup> result = repositoryRegistry.getRepositoryGroups( ).stream( ).filter( filter ).sorted( ordering ).skip( offset ).limit( limit ).map(
                 RepositoryGroup::of
             ).collect( Collectors.toList( ) );
             return new PagedResult<>( totalCount, offset, limit, result );
         }
-        catch ( RepositoryAdminException e )
-        {
-            log.error( "Repository admin error: {}", e.getMessage( ), e );
-            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_ADMIN_ERROR, e.getMessage( ) ) );
-        }
         catch ( ArithmeticException e )
         {
             log.error( "Could not convert total count: {}", e.getMessage( ) );
@@ -126,27 +129,16 @@ public class DefaultRepositoryGroupService implements RepositoryGroupService
         {
             throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_NOT_FOUND, "" ), 404 );
         }
-        try
-        {
-            org.apache.archiva.admin.model.beans.RepositoryGroup group = repositoryGroupAdmin.getRepositoryGroup( repositoryGroupId );
-            return RepositoryGroup.of( group );
-        }
-        catch ( EntityNotFoundException e )
-        {
-            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_NOT_FOUND, repositoryGroupId ), 404 );
-        }
-        catch ( RepositoryAdminException e )
-        {
-            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_ADMIN_ERROR, e.getMessage( ) ) );
-        }
+        org.apache.archiva.repository.RepositoryGroup group = repositoryRegistry.getRepositoryGroup( repositoryGroupId );
+        return RepositoryGroup.of( group );
     }
 
-    private org.apache.archiva.admin.model.beans.RepositoryGroup toModel( RepositoryGroup group )
+    private RepositoryGroupConfiguration toConfig( RepositoryGroup group )
     {
-        org.apache.archiva.admin.model.beans.RepositoryGroup result = new org.apache.archiva.admin.model.beans.RepositoryGroup( );
+        RepositoryGroupConfiguration result = new RepositoryGroupConfiguration( );
         result.setId( group.getId( ) );
         result.setLocation( group.getLocation( ) );
-        result.setRepositories( new ArrayList<>( group.getRepositories( ) ) );
+        result.setRepositories( group.getRepositories( ) );
         result.setMergedIndexPath( group.getMergeConfiguration( ).getMergedIndexPath( ) );
         result.setMergedIndexTtl( group.getMergeConfiguration( ).getMergedIndexTtlMinutes( ) );
         result.setCronExpression( group.getMergeConfiguration( ).getIndexMergeSchedule( ) );
@@ -156,19 +148,29 @@ public class DefaultRepositoryGroupService implements RepositoryGroupService
     @Override
     public RepositoryGroup addRepositoryGroup( RepositoryGroup repositoryGroup ) throws ArchivaRestServiceException
     {
+        final String groupId = repositoryGroup.getId( );
+        if ( StringUtils.isEmpty( groupId ) ) {
+            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_INVALID_ID, groupId ), 422 );
+        }
+        if (repositoryRegistry.hasRepositoryGroup( groupId )) {
+            httpServletResponse.setHeader( "Location", uriInfo.getAbsolutePathBuilder( ).path( groupId ).build( ).toString( ) );
+            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_ID_EXISTS, groupId ), 303 );
+        }
         try
         {
-            Boolean result = repositoryGroupAdmin.addRepositoryGroup( toModel( repositoryGroup ), getAuditData() );
-            if ( result )
+            RepositoryGroupConfiguration configuration = toConfig( repositoryGroup );
+            Configuration config = configurationHandler.getBaseConfiguration( );
+            org.apache.archiva.repository.RepositoryGroup repo = repositoryRegistry.putRepositoryGroup( configuration, config);
+            if ( repo!=null )
             {
-                org.apache.archiva.admin.model.beans.RepositoryGroup newGroup = repositoryGroupAdmin.getRepositoryGroup( repositoryGroup.getId( ) );
-                if ( newGroup != null )
-                {
-                    return RepositoryGroup.of( newGroup );
-                }
-                else
+                ValidationResponse<org.apache.archiva.repository.RepositoryGroup> validationResult = repositoryRegistry.validateRepository( repo );
+                if ( validationResult.isValid( ) )
                 {
-                    throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_ADD_FAILED ) );
+                    httpServletResponse.setStatus( 201 );
+                    configurationHandler.save( config );
+                    return RepositoryGroup.of( repo );
+                } else {
+                    throw ValidationException.of( validationResult );
                 }
             }
             else
@@ -176,75 +178,54 @@ public class DefaultRepositoryGroupService implements RepositoryGroupService
                 throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_ADD_FAILED ) );
             }
         }
-        catch ( EntityExistsException e )
+        catch ( RepositoryException | IndeterminateConfigurationException | RegistryException e )
         {
-            httpServletResponse.setHeader( "Location", uriInfo.getAbsolutePathBuilder( ).path( repositoryGroup.getId( ) ).build( ).toString( ) );
-            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_EXIST, repositoryGroup.getId( ) ), 303 );
-        }
-        catch ( RepositoryAdminException e )
-        {
-            return handleAdminException( e );
-        }
-    }
-
-    private RepositoryGroup handleAdminException( RepositoryAdminException e ) throws ArchivaRestServiceException
-    {
-        log.error( "Repository admin error: {}", e.getMessage( ), e );
-        if ( e.keyExists( ) )
-        {
-            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.PREFIX + e.getKey( ), e.getParameters( ) ) );
-        }
-        else
-        {
-            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_ADMIN_ERROR, e.getMessage( ) ) );
+            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_ADD_FAILED ) );
         }
     }
 
     @Override
-    public RepositoryGroup updateRepositoryGroup( String repositoryGroupId, RepositoryGroup repositoryGroup ) throws ArchivaRestServiceException
+    public RepositoryGroup updateRepositoryGroup( final String repositoryGroupId, final RepositoryGroup repositoryGroup ) throws ArchivaRestServiceException
     {
         if ( StringUtils.isEmpty( repositoryGroupId ) )
         {
             throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_NOT_FOUND, "" ), 404 );
         }
-        org.apache.archiva.admin.model.beans.RepositoryGroup updateGroup = toModel( repositoryGroup );
+        if ( !repositoryRegistry.hasRepositoryGroup( repositoryGroupId ) )
+        {
+            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_NOT_FOUND ), 404 );
+        }
+        repositoryGroup.setId( repositoryGroupId );
+
         try
         {
-            org.apache.archiva.admin.model.beans.RepositoryGroup originGroup = repositoryGroupAdmin.getRepositoryGroup( repositoryGroupId );
-            if ( StringUtils.isEmpty( updateGroup.getId( ) ) )
-            {
-                updateGroup.setId( repositoryGroupId );
-            }
-            if ( StringUtils.isEmpty( updateGroup.getLocation( ) ) )
-            {
-                updateGroup.setLocation( originGroup.getLocation( ) );
-            }
-            if ( StringUtils.isEmpty( updateGroup.getMergedIndexPath( ) ) )
-            {
-                updateGroup.setMergedIndexPath( originGroup.getMergedIndexPath( ) );
-            }
-            if ( updateGroup.getCronExpression( ) == null )
-            {
-                updateGroup.setCronExpression( originGroup.getCronExpression( ) );
-            }
-            if ( updateGroup.getRepositories( ) == null || updateGroup.getRepositories( ).size( ) == 0 )
+            RepositoryGroupConfiguration configuration = toConfig( repositoryGroup );
+            Configuration config = configurationHandler.getBaseConfiguration( );
+            org.apache.archiva.repository.RepositoryGroup repo = repositoryRegistry.putRepositoryGroup( configuration, config);
+            if ( repo!=null )
             {
-                updateGroup.setRepositories( originGroup.getRepositories( ) );
+                ValidationResponse<org.apache.archiva.repository.RepositoryGroup> validationResult = repositoryRegistry.validateRepository( repo );
+                if ( validationResult.isValid( ) )
+                {
+                    httpServletResponse.setStatus( 201 );
+                    configurationHandler.save( config );
+                    return RepositoryGroup.of( repo );
+                } else {
+                    throw ValidationException.of( validationResult );
+                }
             }
-            if ( updateGroup.getMergedIndexTtl( ) <= 0 )
+            else
             {
-                updateGroup.setMergedIndexTtl( originGroup.getMergedIndexTtl( ) );
+                log.error( "Returned repository group was null {}", repositoryGroupId );
+                throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_UPDATE_FAILED ) );
             }
-            repositoryGroupAdmin.updateRepositoryGroup( updateGroup, getAuditData( ) );
-            return RepositoryGroup.of( repositoryGroupAdmin.getRepositoryGroup( repositoryGroupId ) );
-        }
-        catch ( EntityNotFoundException e )
-        {
-            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_NOT_FOUND, repositoryGroupId ), 404 );
+
         }
-        catch ( RepositoryAdminException e )
+        catch ( RepositoryException | IndeterminateConfigurationException | RegistryException e )
         {
-            return handleAdminException( e );
+            log.error( "Exception during repository group update: {}", e.getMessage( ), e );
+            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_UPDATE_FAILED, e.getMessage() ) );
+
         }
     }
 
@@ -257,22 +238,16 @@ public class DefaultRepositoryGroupService implements RepositoryGroupService
         }
         try
         {
-            Boolean deleted = repositoryGroupAdmin.deleteRepositoryGroup( repositoryGroupId, getAuditData( ) );
-            if ( !deleted )
-            {
-                throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_DELETE_FAILED ) );
+            org.apache.archiva.repository.RepositoryGroup group = repositoryRegistry.getRepositoryGroup( repositoryGroupId );
+            if (group==null) {
+                throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_NOT_FOUND, "" ), 404 );
             }
+            repositoryRegistry.removeRepositoryGroup( group );
             return Response.ok( ).build( );
         }
-        catch ( EntityNotFoundException e )
-        {
-            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_NOT_FOUND, repositoryGroupId ), 404 );
-        }
-        catch ( RepositoryAdminException e )
+        catch ( RepositoryException e )
         {
-            handleAdminException( e );
-            // cannot happen:
-            return null;
+            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_DELETE_FAILED ) );
         }
     }
 
@@ -289,28 +264,31 @@ public class DefaultRepositoryGroupService implements RepositoryGroupService
         }
         try
         {
-            repositoryGroupAdmin.addRepositoryToGroup( repositoryGroupId, repositoryId, getAuditData( ) );
-            return RepositoryGroup.of( repositoryGroupAdmin.getRepositoryGroup( repositoryGroupId ) );
-        }
-        catch ( EntityNotFoundException e )
-        {
-            return handleNotFoundException( repositoryGroupId, repositoryId, e );
-        }
-        catch ( EntityExistsException e )
-        {
-            // This is thrown, if the repositoryId is already assigned to the group. We ignore this for the PUT action (nothing to do).
-            try
-            {
-                return RepositoryGroup.of( repositoryGroupAdmin.getRepositoryGroup( repositoryGroupId ) );
+            org.apache.archiva.repository.RepositoryGroup repositoryGroup = repositoryRegistry.getRepositoryGroup( repositoryGroupId );
+            if (repositoryGroup==null) {
+                throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_NOT_FOUND, "" ), 404 );
+            }
+            if (!(repositoryGroup instanceof EditableRepositoryGroup )) {
+                log.error( "This group instance is not editable: {}", repositoryGroupId );
+                throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_UPDATE_FAILED, "" ), 500 );
             }
-            catch ( RepositoryAdminException repositoryAdminException )
+            EditableRepositoryGroup editableRepositoryGroup = (EditableRepositoryGroup) repositoryGroup;
+            if ( editableRepositoryGroup.getRepositories().stream().anyMatch( repo -> repositoryId.equals(repo.getId())) )
             {
-                return handleAdminException( e );
+                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) {
+                throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_NOT_FOUND, "" ), 404 );
             }
+            editableRepositoryGroup.addRepository( managedRepo );
+            org.apache.archiva.repository.RepositoryGroup newGroup = repositoryRegistry.putRepositoryGroup( editableRepositoryGroup );
+            return RepositoryGroup.of( newGroup );
         }
-        catch ( RepositoryAdminException e )
+        catch ( RepositoryException e )
         {
-            return handleAdminException( e );
+            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_UPDATE_FAILED, e.getMessage() ), 500 );
         }
     }
 
@@ -327,34 +305,24 @@ public class DefaultRepositoryGroupService implements RepositoryGroupService
         }
         try
         {
-            repositoryGroupAdmin.deleteRepositoryFromGroup( repositoryGroupId, repositoryId, getAuditData( ) );
-            return RepositoryGroup.of( repositoryGroupAdmin.getRepositoryGroup( repositoryGroupId ) );
-        }
-        catch ( EntityNotFoundException e )
-        {
-            return handleNotFoundException( repositoryGroupId, repositoryId, e );
-        }
-        catch ( RepositoryAdminException e )
-        {
-            return handleAdminException( e );
-        }
-    }
-
-    protected RepositoryGroup handleNotFoundException( String repositoryGroupId, String repositoryId, EntityNotFoundException e ) throws ArchivaRestServiceException
-    {
-        if ( e.getParameters( ).length > 0 )
-        {
-            if ( repositoryGroupId.equals( e.getParameters( )[0] ) )
-            {
-                throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_NOT_FOUND, repositoryGroupId ), 404 );
+            org.apache.archiva.repository.RepositoryGroup repositoryGroup = repositoryRegistry.getRepositoryGroup( repositoryGroupId );
+            if (repositoryGroup==null) {
+                throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_NOT_FOUND, "" ), 404 );
             }
-            else if ( repositoryId.equals( e.getParameters( )[0] ) )
-            {
-                throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_NOT_FOUND, repositoryGroupId ), 404 );
+            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;
+
+            editableRepositoryGroup.removeRepository( repositoryId );
+            org.apache.archiva.repository.RepositoryGroup newGroup = repositoryRegistry.putRepositoryGroup( editableRepositoryGroup );
+            return RepositoryGroup.of( newGroup );
+        }
+        catch ( RepositoryException e )
+        {
+            throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_UPDATE_FAILED, e.getMessage() ), 500 );
         }
-        log.warn( "Entity not found but neither group nor repo set in exception" );
-        throw new ArchivaRestServiceException( ErrorMessage.of( ErrorKeys.REPOSITORY_GROUP_NOT_FOUND, repositoryGroupId ), 404 );
     }