1 package org.apache.archiva.repository.base;
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
20 import org.apache.archiva.components.registry.RegistryException;
21 import org.apache.archiva.configuration.Configuration;
22 import org.apache.archiva.configuration.IndeterminateConfigurationException;
23 import org.apache.archiva.configuration.ManagedRepositoryConfiguration;
24 import org.apache.archiva.configuration.RepositoryGroupConfiguration;
25 import org.apache.archiva.indexer.merger.MergedRemoteIndexesScheduler;
26 import org.apache.archiva.repository.CheckedResult;
27 import org.apache.archiva.repository.EditableRepository;
28 import org.apache.archiva.repository.EditableRepositoryGroup;
29 import org.apache.archiva.repository.ManagedRepository;
30 import org.apache.archiva.repository.RepositoryException;
31 import org.apache.archiva.repository.RepositoryGroup;
32 import org.apache.archiva.repository.RepositoryHandler;
33 import org.apache.archiva.repository.RepositoryProvider;
34 import org.apache.archiva.repository.RepositoryType;
35 import org.apache.archiva.repository.event.RepositoryEvent;
36 import org.apache.archiva.repository.features.IndexCreationFeature;
37 import org.apache.archiva.repository.storage.StorageAsset;
38 import org.apache.archiva.repository.validation.RepositoryChecker;
39 import org.apache.commons.lang3.StringUtils;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42 import org.springframework.stereotype.Service;
44 import javax.annotation.PostConstruct;
45 import javax.annotation.PreDestroy;
46 import javax.inject.Named;
47 import java.io.IOException;
48 import java.nio.file.Files;
49 import java.nio.file.Path;
50 import java.util.Collection;
51 import java.util.Collections;
52 import java.util.HashMap;
53 import java.util.LinkedHashMap;
54 import java.util.List;
56 import java.util.concurrent.locks.ReentrantReadWriteLock;
57 import java.util.stream.Collectors;
59 import static org.apache.archiva.indexer.ArchivaIndexManager.DEFAULT_INDEX_PATH;
62 * This class manages repository groups for the RepositoryRegistry.
63 * It is tightly coupled with the {@link ArchivaRepositoryRegistry}.
65 * @author Martin Stockhammer <martin_s@apache.org>
67 @Service( "repositoryGroupHandler#default" )
68 public class RepositoryGroupHandler implements RepositoryHandler<RepositoryGroup, RepositoryGroupConfiguration>
70 private static final Logger log = LoggerFactory.getLogger( RepositoryGroupHandler.class );
72 private final ArchivaRepositoryRegistry repositoryRegistry;
73 private final ConfigurationHandler configurationHandler;
74 private final MergedRemoteIndexesScheduler mergedRemoteIndexesScheduler;
76 private final Map<String, RepositoryGroup> repositoryGroups = new HashMap<>( );
78 private Path groupsDirectory;
81 * Creates a new instance. All dependencies are injected on the constructor.
83 * @param repositoryRegistry the registry. To avoid circular dependencies via DI, this class registers itself on the registry.
84 * @param configurationHandler the configuration handler is used to retrieve and save configuration.
85 * @param mergedRemoteIndexesScheduler the index scheduler is used for merging the indexes from all group members
87 public RepositoryGroupHandler( ArchivaRepositoryRegistry repositoryRegistry,
88 ConfigurationHandler configurationHandler,
89 @Named( "mergedRemoteIndexesScheduler#default" ) MergedRemoteIndexesScheduler mergedRemoteIndexesScheduler )
91 this.configurationHandler = configurationHandler;
92 this.mergedRemoteIndexesScheduler = mergedRemoteIndexesScheduler;
93 this.repositoryRegistry = repositoryRegistry;
100 log.debug( "Initializing repository group handler " + repositoryRegistry.toString( ) );
101 initializeStorage( );
102 // We are registering this class on the registry. This is necessary to avoid circular dependencies via injection.
103 this.repositoryRegistry.registerGroupHandler( this );
106 public void initializeFromConfig( )
108 this.repositoryGroups.clear( );
109 this.repositoryGroups.putAll( newInstancesFromConfig( ) );
110 for ( RepositoryGroup group : this.repositoryGroups.values( ) )
112 initializeGroup( group );
116 private void initializeStorage( )
118 Path baseDir = this.configurationHandler.getArchivaConfiguration( ).getRepositoryGroupBaseDir( );
119 if ( !Files.exists( baseDir ) )
123 Files.createDirectories( baseDir );
125 catch ( IOException e )
127 log.error( "Could not create group base directory: {}", e.getMessage( ), e );
130 this.groupsDirectory = baseDir;
133 private void initializeGroup( RepositoryGroup repositoryGroup )
135 StorageAsset indexDirectory = getMergedIndexDirectory( repositoryGroup );
136 if ( !indexDirectory.exists( ) )
140 indexDirectory.create( );
142 catch ( IOException e )
144 log.error( "Could not create index directory {} for group {}: {}", indexDirectory, repositoryGroup.getId( ), e.getMessage( ) );
147 Path groupPath = groupsDirectory.resolve( repositoryGroup.getId( ) );
148 if ( !Files.exists( groupPath ) )
152 Files.createDirectories( groupPath );
154 catch ( IOException e )
156 log.error( "Could not create repository group directory {}", groupPath );
159 mergedRemoteIndexesScheduler.schedule( repositoryGroup,
163 public StorageAsset getMergedIndexDirectory( RepositoryGroup group )
167 return group.getFeature( IndexCreationFeature.class ).get( ).getLocalIndexPath( );
177 public Map<String, RepositoryGroup> newInstancesFromConfig( )
181 List<RepositoryGroupConfiguration> repositoryGroupConfigurations =
182 this.configurationHandler.getBaseConfiguration( ).getRepositoryGroups( );
184 if ( repositoryGroupConfigurations == null )
186 return Collections.emptyMap( );
189 Map<String, RepositoryGroup> repositoryGroupMap = new LinkedHashMap<>( repositoryGroupConfigurations.size( ) );
191 Map<RepositoryType, RepositoryProvider> providerMap = repositoryRegistry.getRepositoryProviderMap( );
192 for ( RepositoryGroupConfiguration repoConfig : repositoryGroupConfigurations )
194 RepositoryType repositoryType = RepositoryType.valueOf( repoConfig.getType( ) );
195 if ( providerMap.containsKey( repositoryType ) )
199 RepositoryGroup repo = createNewRepositoryGroup( providerMap.get( repositoryType ), repoConfig );
200 repositoryGroupMap.put( repo.getId( ), repo );
202 catch ( Exception e )
204 log.error( "Could not create repository group {}: {}", repoConfig.getId( ), e.getMessage( ), e );
208 return repositoryGroupMap;
210 catch ( Throwable e )
212 log.error( "Could not initialize repositories from config: {}", e.getMessage( ), e );
213 return Collections.emptyMap( );
218 public RepositoryGroup newInstance( final RepositoryType type, String id ) throws RepositoryException
220 RepositoryProvider provider = repositoryRegistry.getProvider( type );
221 RepositoryGroupConfiguration config = new RepositoryGroupConfiguration( );
223 return createNewRepositoryGroup( provider, config );
227 public RepositoryGroup newInstance( final RepositoryGroupConfiguration repositoryConfiguration ) throws RepositoryException
229 RepositoryType type = RepositoryType.valueOf( repositoryConfiguration.getType( ) );
230 RepositoryProvider provider = repositoryRegistry.getProvider( type );
231 return createNewRepositoryGroup( provider, repositoryConfiguration );
234 private RepositoryGroup createNewRepositoryGroup( RepositoryProvider provider, RepositoryGroupConfiguration config ) throws RepositoryException
236 RepositoryGroup repositoryGroup = provider.createRepositoryGroup( config );
237 updateReferences( repositoryGroup, config );
238 return repositoryGroup;
243 * Adds a new repository group to the current list, or replaces the repository group definition with
244 * the same id, if it exists already.
245 * The change is saved to the configuration immediately.
247 * @param repositoryGroup the new repository group.
248 * @throws RepositoryException if the new repository group could not be saved to the configuration.
251 public RepositoryGroup put( final RepositoryGroup repositoryGroup ) throws RepositoryException
253 final String id = repositoryGroup.getId( );
254 RepositoryGroup originRepoGroup = repositoryGroups.remove( id );
257 if ( originRepoGroup != null && originRepoGroup != repositoryGroup )
259 this.mergedRemoteIndexesScheduler.unschedule( originRepoGroup );
260 originRepoGroup.close( );
262 RepositoryProvider provider = repositoryRegistry.getProvider( repositoryGroup.getType( ) );
263 RepositoryGroupConfiguration newCfg = provider.getRepositoryGroupConfiguration( repositoryGroup );
264 ReentrantReadWriteLock.WriteLock configLock = this.configurationHandler.getLock( ).writeLock( );
268 Configuration configuration = this.configurationHandler.getBaseConfiguration( );
269 updateReferences( repositoryGroup, newCfg );
270 RepositoryGroupConfiguration oldCfg = configuration.findRepositoryGroupById( id );
271 if ( oldCfg != null )
273 configuration.removeRepositoryGroup( oldCfg );
275 configuration.addRepositoryGroup( newCfg );
276 configurationHandler.save( configuration, ConfigurationHandler.REGISTRY_EVENT_TAG );
277 initializeGroup( repositoryGroup );
281 configLock.unlock( );
283 repositoryGroups.put( id, repositoryGroup );
284 return repositoryGroup;
286 catch ( Exception e )
289 if ( originRepoGroup != null )
291 repositoryGroups.put( id, originRepoGroup );
295 repositoryGroups.remove( id );
297 log.error( "Exception during configuration update {}", e.getMessage( ), e );
298 throw new RepositoryException( "Could not save the configuration" + ( e.getMessage( ) == null ? "" : ": " + e.getMessage( ) ), e);
303 * Adds a new repository group or updates the repository with the same id, if it exists already.
304 * The configuration is saved immediately.
306 * @param repositoryGroupConfiguration the repository configuration
307 * @return the updated or created repository
308 * @throws RepositoryException if an error occurs, or the configuration is not valid.
311 public RepositoryGroup put( RepositoryGroupConfiguration repositoryGroupConfiguration ) throws RepositoryException
313 final String id = repositoryGroupConfiguration.getId( );
314 final RepositoryType repositoryType = RepositoryType.valueOf( repositoryGroupConfiguration.getType( ) );
315 final RepositoryProvider provider = repositoryRegistry.getProvider( repositoryType );
316 RepositoryGroup currentRepository;
317 ReentrantReadWriteLock.WriteLock configLock = this.configurationHandler.getLock( ).writeLock( );
321 Configuration configuration = this.configurationHandler.getBaseConfiguration( );
322 currentRepository = repositoryRegistry.getRepositoryGroup( id );
323 RepositoryGroup oldRepository = currentRepository == null ? null : clone( currentRepository );
327 if (currentRepository==null) {
328 currentRepository = put( repositoryGroupConfiguration, configuration );
331 setRepositoryGroupDefaults( repositoryGroupConfiguration );
332 provider.updateRepositoryGroupInstance( (EditableRepositoryGroup) currentRepository, repositoryGroupConfiguration );
334 configurationHandler.save( configuration, ConfigurationHandler.REGISTRY_EVENT_TAG );
335 updateReferences( currentRepository, repositoryGroupConfiguration );
336 initializeGroup( currentRepository );
337 this.repositoryGroups.put( id, currentRepository );
339 catch ( IndeterminateConfigurationException | RegistryException | RepositoryException e )
342 if ( oldRepository != null )
344 RepositoryGroupConfiguration oldCfg = provider.getRepositoryGroupConfiguration( oldRepository );
345 provider.updateRepositoryGroupInstance( (EditableRepositoryGroup) currentRepository, oldCfg);
346 replaceOrAddRepositoryConfig( oldCfg, configuration );
349 configurationHandler.save( configuration, ConfigurationHandler.REGISTRY_EVENT_TAG );
351 catch ( IndeterminateConfigurationException | RegistryException indeterminateConfigurationException )
353 log.error( "Fatal error, config save during rollback failed: {}", e.getMessage( ), e );
355 updateReferences( oldRepository, oldCfg );
356 initializeGroup( oldRepository );
358 log.error( "Could not save the configuration for repository group {}: {}", id, e.getMessage( ), e );
359 if (e instanceof RepositoryException) {
360 throw (RepositoryException) e;
363 throw new RepositoryException( "Could not save the configuration for repository group " + id + ": " + e.getMessage( ) );
369 configLock.unlock( );
371 return currentRepository;
375 public RepositoryGroup put( RepositoryGroupConfiguration repositoryGroupConfiguration, Configuration configuration ) throws RepositoryException
377 final String id = repositoryGroupConfiguration.getId( );
378 final RepositoryType repoType = RepositoryType.valueOf( repositoryGroupConfiguration.getType( ) );
379 RepositoryGroup repo;
380 setRepositoryGroupDefaults( repositoryGroupConfiguration );
381 if ( repositoryGroups.containsKey( id ) )
383 repo = clone( repositoryGroups.get( id ) );
384 if ( repo instanceof EditableRepositoryGroup )
386 repositoryRegistry.getProvider( repoType ).updateRepositoryGroupInstance( (EditableRepositoryGroup) repo, repositoryGroupConfiguration );
390 throw new RepositoryException( "The repository is not editable " + id );
395 repo = repositoryRegistry.getProvider( repoType ).createRepositoryGroup( repositoryGroupConfiguration );
397 replaceOrAddRepositoryConfig( repositoryGroupConfiguration, configuration );
402 public <D> CheckedResult<RepositoryGroup, D> putWithCheck( RepositoryGroupConfiguration repositoryConfiguration, RepositoryChecker<RepositoryGroup, D> checker ) throws RepositoryException
404 final String id = repositoryConfiguration.getId( );
405 RepositoryGroup currentGroup = repositoryGroups.get( id );
406 Configuration configuration = configurationHandler.getBaseConfiguration( );
407 RepositoryGroup repositoryGroup = put( repositoryConfiguration, configuration );
408 CheckedResult<RepositoryGroup, D> result;
409 if ( currentGroup == null )
411 result = checker.apply( repositoryGroup );
415 result = checker.applyForUpdate( repositoryGroup );
417 if ( result.isValid( ) )
419 put( repositoryConfiguration );
425 private void setRepositoryGroupDefaults( RepositoryGroupConfiguration repositoryGroupConfiguration )
427 if ( StringUtils.isEmpty( repositoryGroupConfiguration.getMergedIndexPath( ) ) )
429 repositoryGroupConfiguration.setMergedIndexPath( DEFAULT_INDEX_PATH );
431 if ( repositoryGroupConfiguration.getMergedIndexTtl( ) <= 0 )
433 repositoryGroupConfiguration.setMergedIndexTtl( 300 );
435 if ( StringUtils.isEmpty( repositoryGroupConfiguration.getCronExpression( ) ) )
437 repositoryGroupConfiguration.setCronExpression( "0 0 03 ? * MON" );
441 private void replaceOrAddRepositoryConfig( RepositoryGroupConfiguration repositoryGroupConfiguration, Configuration configuration )
443 RepositoryGroupConfiguration oldCfg = configuration.findRepositoryGroupById( repositoryGroupConfiguration.getId( ) );
444 if ( oldCfg != null )
446 configuration.removeRepositoryGroup( oldCfg );
448 configuration.addRepositoryGroup( repositoryGroupConfiguration );
451 public void removeRepositoryFromGroups( ManagedRepository repo )
455 repositoryGroups.values( ).stream( ).filter( repoGroup -> repoGroup instanceof EditableRepository ).
456 map( repoGroup -> (EditableRepositoryGroup) repoGroup ).forEach( repoGroup -> repoGroup.removeRepository( repo ) );
461 * Removes a repository group from the registry and configuration, if it exists.
462 * The change is saved to the configuration immediately.
464 * @param id the id of the repository group to remove
465 * @throws RepositoryException if a error occurs during configuration save
468 public void remove( final String id ) throws RepositoryException
470 RepositoryGroup repo = get( id );
475 repo = repositoryGroups.remove( id );
478 this.mergedRemoteIndexesScheduler.unschedule( repo );
480 Configuration configuration = this.configurationHandler.getBaseConfiguration( );
481 RepositoryGroupConfiguration cfg = configuration.findRepositoryGroupById( id );
484 configuration.removeRepositoryGroup( cfg );
486 this.configurationHandler.save( configuration, ConfigurationHandler.REGISTRY_EVENT_TAG );
490 catch ( RegistryException | IndeterminateConfigurationException e )
493 log.error( "Could not save config after repository removal: {}", e.getMessage( ), e );
494 repositoryGroups.put( repo.getId( ), repo );
495 throw new RepositoryException( "Could not save configuration after repository removal: " + e.getMessage( ) );
501 public void remove( String id, Configuration configuration ) throws RepositoryException
503 RepositoryGroup repo = repositoryGroups.get( id );
506 repo = repositoryGroups.remove( id );
509 this.mergedRemoteIndexesScheduler.unschedule( repo );
511 RepositoryGroupConfiguration cfg = configuration.findRepositoryGroupById( id );
514 configuration.removeRepositoryGroup( cfg );
522 public RepositoryGroup get( String groupId )
524 return repositoryGroups.get( groupId );
528 public RepositoryGroup clone( RepositoryGroup repo ) throws RepositoryException
530 RepositoryProvider provider = repositoryRegistry.getProvider( repo.getType( ) );
531 RepositoryGroupConfiguration cfg = provider.getRepositoryGroupConfiguration( repo );
532 RepositoryGroup cloned = provider.createRepositoryGroup( cfg );
533 cloned.registerEventHandler( RepositoryEvent.ANY, repositoryRegistry );
538 public void updateReferences( RepositoryGroup repo, RepositoryGroupConfiguration repositoryConfiguration ) throws RepositoryException
540 if ( repo instanceof EditableRepositoryGroup )
542 EditableRepositoryGroup eGroup = (EditableRepositoryGroup) repo;
543 eGroup.setRepositories( repositoryConfiguration.getRepositories( ).stream( )
544 .map( repositoryRegistry::getManagedRepository ).collect( Collectors.toList( ) ) );
550 public Collection<RepositoryGroup> getAll( )
552 return repositoryGroups.values( );
556 public boolean has( String id )
558 return repositoryGroups.containsKey( id );
562 private void destroy( )
570 for ( RepositoryGroup group : repositoryGroups.values( ) )
574 mergedRemoteIndexesScheduler.unschedule( group );
577 catch ( Throwable e )
579 log.error( "Could not close repository group {}: {}", group.getId( ), e.getMessage( ) );
582 this.repositoryGroups.clear( );