diff options
author | Martin Stockhammer <martin_s@apache.org> | 2021-06-27 11:35:23 +0200 |
---|---|---|
committer | Martin Stockhammer <martin_s@apache.org> | 2021-06-27 11:35:23 +0200 |
commit | e8a70027d8342f78272b471aa0ac963c0a6f89be (patch) | |
tree | 26ff0e859616e117d65103ebbea11ddf50b43c87 | |
parent | a1b92c562fae957e601875e50f87a3ff93fecfd6 (diff) | |
download | archiva-e8a70027d8342f78272b471aa0ac963c0a6f89be.tar.gz archiva-e8a70027d8342f78272b471aa0ac963c0a6f89be.zip |
Changing repository group handling
26 files changed, 2095 insertions, 439 deletions
diff --git a/archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/archiva/configuration/RepositoryGroupConfiguration.java b/archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/archiva/configuration/RepositoryGroupConfiguration.java index cf518951c..d0dc381e4 100644 --- a/archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/archiva/configuration/RepositoryGroupConfiguration.java +++ b/archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/archiva/configuration/RepositoryGroupConfiguration.java @@ -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. * diff --git a/archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/archiva/configuration/io/registry/ConfigurationRegistryReader.java b/archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/archiva/configuration/io/registry/ConfigurationRegistryReader.java index f3f0f2ea7..27146f5e6 100644 --- a/archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/archiva/configuration/io/registry/ConfigurationRegistryReader.java +++ b/archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/archiva/configuration/io/registry/ConfigurationRegistryReader.java @@ -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()) { diff --git a/archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/archiva/configuration/io/registry/ConfigurationRegistryWriter.java b/archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/archiva/configuration/io/registry/ConfigurationRegistryWriter.java index 2cbd387b6..83202f531 100644 --- a/archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/archiva/configuration/io/registry/ConfigurationRegistryWriter.java +++ b/archiva-modules/archiva-base/archiva-configuration/src/main/java/org/apache/archiva/configuration/io/registry/ConfigurationRegistryWriter.java @@ -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 index 000000000..41961a5e7 --- /dev/null +++ b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/CheckedResult.java @@ -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 index 000000000..49fc4de12 --- /dev/null +++ b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/RepositoryHandler.java @@ -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(); + +} diff --git a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/RepositoryRegistry.java b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/RepositoryRegistry.java index bb6bbc98b..2a5210666 100644 --- a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/RepositoryRegistry.java +++ b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/RepositoryRegistry.java @@ -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/RepositoryType.java b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/RepositoryType.java index f60c65787..b5e8385c1 100644 --- a/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/RepositoryType.java +++ b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/RepositoryType.java @@ -25,5 +25,5 @@ package org.apache.archiva.repository; */ public enum RepositoryType { - MAVEN, NPM + ALL, MAVEN, NPM } 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 index 000000000..319fc50db --- /dev/null +++ b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/validation/AbstractRepositoryValidator.java @@ -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 index 000000000..f208d10fe --- /dev/null +++ b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/validation/RepositoryChecker.java @@ -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 index 000000000..041fe8a48 --- /dev/null +++ b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/validation/RepositoryValidator.java @@ -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 index 000000000..22594fbfa --- /dev/null +++ b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/validation/ValidationError.java @@ -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 index 000000000..377a3ccb2 --- /dev/null +++ b/archiva-modules/archiva-base/archiva-repository-api/src/main/java/org/apache/archiva/repository/validation/ValidationResponse.java @@ -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( ); + } + } + +} diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/ArchivaRepositoryRegistry.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/ArchivaRepositoryRegistry.java index 249436092..db1d626a2 100644 --- a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/ArchivaRepositoryRegistry.java +++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/ArchivaRepositoryRegistry.java @@ -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 ); diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/ConfigurationHandler.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/ConfigurationHandler.java index c4ee51370..4c21083e9 100644 --- a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/ConfigurationHandler.java +++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/ConfigurationHandler.java @@ -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; + } } diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/RepositoryGroupHandler.java b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/RepositoryGroupHandler.java index 9a08e76ae..376be6665 100644 --- a/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/RepositoryGroupHandler.java +++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/RepositoryGroupHandler.java @@ -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 index 000000000..f9fd6fd1e --- /dev/null +++ b/archiva-modules/archiva-base/archiva-repository-layer/src/main/java/org/apache/archiva/repository/base/validation/CommonGroupValidator.java @@ -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 ); + } +} diff --git a/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/base/ArchivaRepositoryRegistryTest.java b/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/base/ArchivaRepositoryRegistryTest.java index b7cbf1fb5..00bbbc8c1 100644 --- a/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/base/ArchivaRepositoryRegistryTest.java +++ b/archiva-modules/archiva-base/archiva-repository-layer/src/test/java/org/apache/archiva/repository/base/ArchivaRepositoryRegistryTest.java @@ -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 diff --git a/archiva-modules/archiva-maven/archiva-maven-repository/src/main/java/org/apache/archiva/repository/maven/MavenRepositoryProvider.java b/archiva-modules/archiva-maven/archiva-maven-repository/src/main/java/org/apache/archiva/repository/maven/MavenRepositoryProvider.java index 2afc249bb..1deadfe38 100644 --- a/archiva-modules/archiva-maven/archiva-maven-repository/src/main/java/org/apache/archiva/repository/maven/MavenRepositoryProvider.java +++ b/archiva-modules/archiva-maven/archiva-maven-repository/src/main/java/org/apache/archiva/repository/maven/MavenRepositoryProvider.java @@ -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); diff --git a/archiva-modules/archiva-maven/archiva-maven-repository/src/test/java/org/apache/archiva/repository/maven/mock/RepositoryRegistryMock.java b/archiva-modules/archiva-maven/archiva-maven-repository/src/test/java/org/apache/archiva/repository/maven/mock/RepositoryRegistryMock.java index 4ceed3865..c9fba72a6 100644 --- a/archiva-modules/archiva-maven/archiva-maven-repository/src/test/java/org/apache/archiva/repository/maven/mock/RepositoryRegistryMock.java +++ b/archiva-modules/archiva-maven/archiva-maven-repository/src/test/java/org/apache/archiva/repository/maven/mock/RepositoryRegistryMock.java @@ -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 diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/RepositoryGroup.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/RepositoryGroup.java index 7eea24702..1f021d0f4 100644 --- a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/RepositoryGroup.java +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/RepositoryGroup.java @@ -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 index 000000000..efa1ec987 --- /dev/null +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/model/v2/ValidationError.java @@ -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; + } +} diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/ArchivaRestError.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/ArchivaRestError.java index 0df947505..17c5b89d2 100644 --- a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/ArchivaRestError.java +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/ArchivaRestError.java @@ -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( ); + } } diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/ErrorKeys.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/ErrorKeys.java index ab74ceaec..fee06c9e7 100644 --- a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/ErrorKeys.java +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/ErrorKeys.java @@ -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"; diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/RepositoryGroupService.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/RepositoryGroupService.java index a95c68f95..1c0b0595e 100644 --- a/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/RepositoryGroupService.java +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/RepositoryGroupService.java @@ -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 index 000000000..bedb46d48 --- /dev/null +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-api/src/main/java/org/apache/archiva/rest/api/services/v2/ValidationException.java @@ -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 ); + } +} diff --git a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/v2/DefaultRepositoryGroupService.java b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/v2/DefaultRepositoryGroupService.java index 44ce638ac..4535336d6 100644 --- a/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/v2/DefaultRepositoryGroupService.java +++ b/archiva-modules/archiva-web/archiva-rest/archiva-rest-services/src/main/java/org/apache/archiva/rest/services/v2/DefaultRepositoryGroupService.java @@ -34,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 ); } |