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