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