]> source.dussan.org Git - archiva.git/blob
376be6665bcddc87df75ae4344a7387377d2450e
[archiva.git] /
1 package org.apache.archiva.repository.base;
2 /*
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
10  *
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
17  * under the License.
18  */
19
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;
43
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;
55 import java.util.Map;
56 import java.util.concurrent.locks.ReentrantReadWriteLock;
57 import java.util.stream.Collectors;
58
59 import static org.apache.archiva.indexer.ArchivaIndexManager.DEFAULT_INDEX_PATH;
60
61 /**
62  * This class manages repository groups for the RepositoryRegistry.
63  * It is tightly coupled with the {@link ArchivaRepositoryRegistry}.
64  *
65  * @author Martin Stockhammer <martin_s@apache.org>
66  */
67 @Service( "repositoryGroupHandler#default" )
68 public class RepositoryGroupHandler implements RepositoryHandler<RepositoryGroup, RepositoryGroupConfiguration>
69 {
70     private static final Logger log = LoggerFactory.getLogger( RepositoryGroupHandler.class );
71
72     private final ArchivaRepositoryRegistry repositoryRegistry;
73     private final ConfigurationHandler configurationHandler;
74     private final MergedRemoteIndexesScheduler mergedRemoteIndexesScheduler;
75
76     private final Map<String, RepositoryGroup> repositoryGroups = new HashMap<>( );
77
78     private Path groupsDirectory;
79
80     /**
81      * Creates a new instance. All dependencies are injected on the constructor.
82      *
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
86      */
87     public RepositoryGroupHandler( ArchivaRepositoryRegistry repositoryRegistry,
88                                    ConfigurationHandler configurationHandler,
89                                    @Named( "mergedRemoteIndexesScheduler#default" ) MergedRemoteIndexesScheduler mergedRemoteIndexesScheduler )
90     {
91         this.configurationHandler = configurationHandler;
92         this.mergedRemoteIndexesScheduler = mergedRemoteIndexesScheduler;
93         this.repositoryRegistry = repositoryRegistry;
94     }
95
96     @Override
97     @PostConstruct
98     public void init( )
99     {
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 );
104     }
105
106     public void initializeFromConfig( )
107     {
108         this.repositoryGroups.clear( );
109         this.repositoryGroups.putAll( newInstancesFromConfig( ) );
110         for ( RepositoryGroup group : this.repositoryGroups.values( ) )
111         {
112             initializeGroup( group );
113         }
114     }
115
116     private void initializeStorage( )
117     {
118         Path baseDir = this.configurationHandler.getArchivaConfiguration( ).getRepositoryGroupBaseDir( );
119         if ( !Files.exists( baseDir ) )
120         {
121             try
122             {
123                 Files.createDirectories( baseDir );
124             }
125             catch ( IOException e )
126             {
127                 log.error( "Could not create group base directory: {}", e.getMessage( ), e );
128             }
129         }
130         this.groupsDirectory = baseDir;
131     }
132
133     private void initializeGroup( RepositoryGroup repositoryGroup )
134     {
135         StorageAsset indexDirectory = getMergedIndexDirectory( repositoryGroup );
136         if ( !indexDirectory.exists( ) )
137         {
138             try
139             {
140                 indexDirectory.create( );
141             }
142             catch ( IOException e )
143             {
144                 log.error( "Could not create index directory {} for group {}: {}", indexDirectory, repositoryGroup.getId( ), e.getMessage( ) );
145             }
146         }
147         Path groupPath = groupsDirectory.resolve( repositoryGroup.getId( ) );
148         if ( !Files.exists( groupPath ) )
149         {
150             try
151             {
152                 Files.createDirectories( groupPath );
153             }
154             catch ( IOException e )
155             {
156                 log.error( "Could not create repository group directory {}", groupPath );
157             }
158         }
159         mergedRemoteIndexesScheduler.schedule( repositoryGroup,
160             indexDirectory );
161     }
162
163     public StorageAsset getMergedIndexDirectory( RepositoryGroup group )
164     {
165         if ( group != null )
166         {
167             return group.getFeature( IndexCreationFeature.class ).get( ).getLocalIndexPath( );
168         }
169         else
170         {
171             return null;
172         }
173     }
174
175
176     @Override
177     public Map<String, RepositoryGroup> newInstancesFromConfig( )
178     {
179         try
180         {
181             List<RepositoryGroupConfiguration> repositoryGroupConfigurations =
182                 this.configurationHandler.getBaseConfiguration( ).getRepositoryGroups( );
183
184             if ( repositoryGroupConfigurations == null )
185             {
186                 return Collections.emptyMap( );
187             }
188
189             Map<String, RepositoryGroup> repositoryGroupMap = new LinkedHashMap<>( repositoryGroupConfigurations.size( ) );
190
191             Map<RepositoryType, RepositoryProvider> providerMap = repositoryRegistry.getRepositoryProviderMap( );
192             for ( RepositoryGroupConfiguration repoConfig : repositoryGroupConfigurations )
193             {
194                 RepositoryType repositoryType = RepositoryType.valueOf( repoConfig.getType( ) );
195                 if ( providerMap.containsKey( repositoryType ) )
196                 {
197                     try
198                     {
199                         RepositoryGroup repo = createNewRepositoryGroup( providerMap.get( repositoryType ), repoConfig );
200                         repositoryGroupMap.put( repo.getId( ), repo );
201                     }
202                     catch ( Exception e )
203                     {
204                         log.error( "Could not create repository group {}: {}", repoConfig.getId( ), e.getMessage( ), e );
205                     }
206                 }
207             }
208             return repositoryGroupMap;
209         }
210         catch ( Throwable e )
211         {
212             log.error( "Could not initialize repositories from config: {}", e.getMessage( ), e );
213             return Collections.emptyMap( );
214         }
215     }
216
217     @Override
218     public RepositoryGroup newInstance( final RepositoryType type, String id ) throws RepositoryException
219     {
220         RepositoryProvider provider = repositoryRegistry.getProvider( type );
221         RepositoryGroupConfiguration config = new RepositoryGroupConfiguration( );
222         config.setId( id );
223         return createNewRepositoryGroup( provider, config );
224     }
225
226     @Override
227     public RepositoryGroup newInstance( final RepositoryGroupConfiguration repositoryConfiguration ) throws RepositoryException
228     {
229         RepositoryType type = RepositoryType.valueOf( repositoryConfiguration.getType( ) );
230         RepositoryProvider provider = repositoryRegistry.getProvider( type );
231         return createNewRepositoryGroup( provider, repositoryConfiguration );
232     }
233
234     private RepositoryGroup createNewRepositoryGroup( RepositoryProvider provider, RepositoryGroupConfiguration config ) throws RepositoryException
235     {
236         RepositoryGroup repositoryGroup = provider.createRepositoryGroup( config );
237         updateReferences( repositoryGroup, config );
238         return repositoryGroup;
239     }
240
241
242     /**
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.
246      *
247      * @param repositoryGroup the new repository group.
248      * @throws RepositoryException if the new repository group could not be saved to the configuration.
249      */
250     @Override
251     public RepositoryGroup put( final RepositoryGroup repositoryGroup ) throws RepositoryException
252     {
253         final String id = repositoryGroup.getId( );
254         RepositoryGroup originRepoGroup = repositoryGroups.remove( id );
255         try
256         {
257             if ( originRepoGroup != null && originRepoGroup != repositoryGroup )
258             {
259                 this.mergedRemoteIndexesScheduler.unschedule( originRepoGroup );
260                 originRepoGroup.close( );
261             }
262             RepositoryProvider provider = repositoryRegistry.getProvider( repositoryGroup.getType( ) );
263             RepositoryGroupConfiguration newCfg = provider.getRepositoryGroupConfiguration( repositoryGroup );
264             ReentrantReadWriteLock.WriteLock configLock = this.configurationHandler.getLock( ).writeLock( );
265             configLock.lock( );
266             try
267             {
268                 Configuration configuration = this.configurationHandler.getBaseConfiguration( );
269                 updateReferences( repositoryGroup, newCfg );
270                 RepositoryGroupConfiguration oldCfg = configuration.findRepositoryGroupById( id );
271                 if ( oldCfg != null )
272                 {
273                     configuration.removeRepositoryGroup( oldCfg );
274                 }
275                 configuration.addRepositoryGroup( newCfg );
276                 configurationHandler.save( configuration, ConfigurationHandler.REGISTRY_EVENT_TAG );
277                 initializeGroup( repositoryGroup );
278             }
279             finally
280             {
281                 configLock.unlock( );
282             }
283             repositoryGroups.put( id, repositoryGroup );
284             return repositoryGroup;
285         }
286         catch ( Exception e )
287         {
288             // Rollback
289             if ( originRepoGroup != null )
290             {
291                 repositoryGroups.put( id, originRepoGroup );
292             }
293             else
294             {
295                 repositoryGroups.remove( id );
296             }
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);
299         }
300     }
301
302     /**
303      * Adds a new repository group or updates the repository with the same id, if it exists already.
304      * The configuration is saved immediately.
305      *
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.
309      */
310     @Override
311     public RepositoryGroup put( RepositoryGroupConfiguration repositoryGroupConfiguration ) throws RepositoryException
312     {
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( );
318         configLock.lock( );
319         try
320         {
321             Configuration configuration = this.configurationHandler.getBaseConfiguration( );
322             currentRepository = repositoryRegistry.getRepositoryGroup( id );
323             RepositoryGroup oldRepository = currentRepository == null ? null : clone( currentRepository );
324             try
325             {
326
327                 if (currentRepository==null) {
328                     currentRepository = put( repositoryGroupConfiguration, configuration );
329                 } else
330                 {
331                     setRepositoryGroupDefaults( repositoryGroupConfiguration );
332                     provider.updateRepositoryGroupInstance( (EditableRepositoryGroup) currentRepository, repositoryGroupConfiguration );
333                 }
334                 configurationHandler.save( configuration, ConfigurationHandler.REGISTRY_EVENT_TAG );
335                 updateReferences( currentRepository, repositoryGroupConfiguration );
336                 initializeGroup( currentRepository );
337                 this.repositoryGroups.put( id, currentRepository );
338             }
339             catch ( IndeterminateConfigurationException | RegistryException | RepositoryException e )
340             {
341                 // Trying a rollback
342                 if ( oldRepository != null  )
343                 {
344                     RepositoryGroupConfiguration oldCfg = provider.getRepositoryGroupConfiguration( oldRepository );
345                     provider.updateRepositoryGroupInstance( (EditableRepositoryGroup) currentRepository, oldCfg);
346                     replaceOrAddRepositoryConfig( oldCfg, configuration );
347                     try
348                     {
349                         configurationHandler.save( configuration, ConfigurationHandler.REGISTRY_EVENT_TAG );
350                     }
351                     catch ( IndeterminateConfigurationException | RegistryException indeterminateConfigurationException )
352                     {
353                         log.error( "Fatal error, config save during rollback failed: {}", e.getMessage( ), e );
354                     }
355                     updateReferences( oldRepository, oldCfg  );
356                     initializeGroup( oldRepository );
357                 }
358                 log.error( "Could not save the configuration for repository group {}: {}", id, e.getMessage( ), e );
359                 if (e instanceof RepositoryException) {
360                     throw (RepositoryException) e;
361                 } else
362                 {
363                     throw new RepositoryException( "Could not save the configuration for repository group " + id + ": " + e.getMessage( ) );
364                 }
365             }
366         }
367         finally
368         {
369             configLock.unlock( );
370         }
371         return currentRepository;
372     }
373
374     @Override
375     public RepositoryGroup put( RepositoryGroupConfiguration repositoryGroupConfiguration, Configuration configuration ) throws RepositoryException
376     {
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 ) )
382         {
383             repo = clone( repositoryGroups.get( id ) );
384             if ( repo instanceof EditableRepositoryGroup )
385             {
386                 repositoryRegistry.getProvider( repoType ).updateRepositoryGroupInstance( (EditableRepositoryGroup) repo, repositoryGroupConfiguration );
387             }
388             else
389             {
390                 throw new RepositoryException( "The repository is not editable " + id );
391             }
392         }
393         else
394         {
395             repo = repositoryRegistry.getProvider( repoType ).createRepositoryGroup( repositoryGroupConfiguration );
396         }
397         replaceOrAddRepositoryConfig( repositoryGroupConfiguration, configuration );
398         return repo;
399     }
400
401     @Override
402     public <D> CheckedResult<RepositoryGroup, D> putWithCheck( RepositoryGroupConfiguration repositoryConfiguration, RepositoryChecker<RepositoryGroup, D> checker ) throws RepositoryException
403     {
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 )
410         {
411             result = checker.apply( repositoryGroup );
412         }
413         else
414         {
415             result = checker.applyForUpdate( repositoryGroup );
416         }
417         if ( result.isValid( ) )
418         {
419             put( repositoryConfiguration );
420         }
421         return result;
422     }
423
424
425     private void setRepositoryGroupDefaults( RepositoryGroupConfiguration repositoryGroupConfiguration )
426     {
427         if ( StringUtils.isEmpty( repositoryGroupConfiguration.getMergedIndexPath( ) ) )
428         {
429             repositoryGroupConfiguration.setMergedIndexPath( DEFAULT_INDEX_PATH );
430         }
431         if ( repositoryGroupConfiguration.getMergedIndexTtl( ) <= 0 )
432         {
433             repositoryGroupConfiguration.setMergedIndexTtl( 300 );
434         }
435         if ( StringUtils.isEmpty( repositoryGroupConfiguration.getCronExpression( ) ) )
436         {
437             repositoryGroupConfiguration.setCronExpression( "0 0 03 ? * MON" );
438         }
439     }
440
441     private void replaceOrAddRepositoryConfig( RepositoryGroupConfiguration repositoryGroupConfiguration, Configuration configuration )
442     {
443         RepositoryGroupConfiguration oldCfg = configuration.findRepositoryGroupById( repositoryGroupConfiguration.getId( ) );
444         if ( oldCfg != null )
445         {
446             configuration.removeRepositoryGroup( oldCfg );
447         }
448         configuration.addRepositoryGroup( repositoryGroupConfiguration );
449     }
450
451     public void removeRepositoryFromGroups( ManagedRepository repo )
452     {
453         if ( repo != null )
454         {
455             repositoryGroups.values( ).stream( ).filter( repoGroup -> repoGroup instanceof EditableRepository ).
456                 map( repoGroup -> (EditableRepositoryGroup) repoGroup ).forEach( repoGroup -> repoGroup.removeRepository( repo ) );
457         }
458     }
459
460     /**
461      * Removes a repository group from the registry and configuration, if it exists.
462      * The change is saved to the configuration immediately.
463      *
464      * @param id the id of the repository group to remove
465      * @throws RepositoryException if a error occurs during configuration save
466      */
467     @Override
468     public void remove( final String id ) throws RepositoryException
469     {
470         RepositoryGroup repo = get( id );
471         if ( repo != null )
472         {
473             try
474             {
475                 repo = repositoryGroups.remove( id );
476                 if ( repo != null )
477                 {
478                     this.mergedRemoteIndexesScheduler.unschedule( repo );
479                     repo.close( );
480                     Configuration configuration = this.configurationHandler.getBaseConfiguration( );
481                     RepositoryGroupConfiguration cfg = configuration.findRepositoryGroupById( id );
482                     if ( cfg != null )
483                     {
484                         configuration.removeRepositoryGroup( cfg );
485                     }
486                     this.configurationHandler.save( configuration, ConfigurationHandler.REGISTRY_EVENT_TAG );
487                 }
488
489             }
490             catch ( RegistryException | IndeterminateConfigurationException e )
491             {
492                 // Rollback
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( ) );
496             }
497         }
498     }
499
500     @Override
501     public void remove( String id, Configuration configuration ) throws RepositoryException
502     {
503         RepositoryGroup repo = repositoryGroups.get( id );
504         if ( repo != null )
505         {
506             repo = repositoryGroups.remove( id );
507             if ( repo != null )
508             {
509                 this.mergedRemoteIndexesScheduler.unschedule( repo );
510                 repo.close( );
511                 RepositoryGroupConfiguration cfg = configuration.findRepositoryGroupById( id );
512                 if ( cfg != null )
513                 {
514                     configuration.removeRepositoryGroup( cfg );
515                 }
516             }
517         }
518
519     }
520
521     @Override
522     public RepositoryGroup get( String groupId )
523     {
524         return repositoryGroups.get( groupId );
525     }
526
527     @Override
528     public RepositoryGroup clone( RepositoryGroup repo ) throws RepositoryException
529     {
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 );
534         return cloned;
535     }
536
537     @Override
538     public void updateReferences( RepositoryGroup repo, RepositoryGroupConfiguration repositoryConfiguration ) throws RepositoryException
539     {
540         if ( repo instanceof EditableRepositoryGroup )
541         {
542             EditableRepositoryGroup eGroup = (EditableRepositoryGroup) repo;
543             eGroup.setRepositories( repositoryConfiguration.getRepositories( ).stream( )
544                 .map( repositoryRegistry::getManagedRepository ).collect( Collectors.toList( ) ) );
545         }
546
547     }
548
549     @Override
550     public Collection<RepositoryGroup> getAll( )
551     {
552         return repositoryGroups.values( );
553     }
554
555     @Override
556     public boolean has( String id )
557     {
558         return repositoryGroups.containsKey( id );
559     }
560
561     @PreDestroy
562     private void destroy( )
563     {
564         this.close( );
565     }
566
567     @Override
568     public void close( )
569     {
570         for ( RepositoryGroup group : repositoryGroups.values( ) )
571         {
572             try
573             {
574                 mergedRemoteIndexesScheduler.unschedule( group );
575                 group.close( );
576             }
577             catch ( Throwable e )
578             {
579                 log.error( "Could not close repository group {}: {}", group.getId( ), e.getMessage( ) );
580             }
581         }
582         this.repositoryGroups.clear( );
583     }
584
585 }