]> source.dussan.org Git - archiva.git/blob
1dd0baeb24000b1f6b0aa9bf4664334a81da4bf6
[archiva.git] /
1 package org.apache.archiva.configuration;
2
3 /*
4  * Licensed to the Apache Software Foundation (ASF) under one
5  * or more contributor license agreements.  See the NOTICE file
6  * distributed with this work for additional information
7  * regarding copyright ownership.  The ASF licenses this file
8  * to you under the Apache License, Version 2.0 (the
9  * "License"); you may not use this file except in compliance
10  * with the License.  You may obtain a copy of the License at
11  *
12  *   http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  * KIND, either express or implied.  See the License for the
18  * specific language governing permissions and limitations
19  * under the License.
20  */
21
22 import org.apache.archiva.configuration.functors.ProxyConnectorConfigurationOrderComparator;
23 import org.apache.archiva.configuration.io.registry.ConfigurationRegistryReader;
24 import org.apache.archiva.configuration.io.registry.ConfigurationRegistryWriter;
25 import org.apache.archiva.policies.AbstractUpdatePolicy;
26 import org.apache.archiva.policies.CachedFailuresPolicy;
27 import org.apache.archiva.policies.ChecksumPolicy;
28 import org.apache.archiva.policies.DownloadErrorPolicy;
29 import org.apache.archiva.policies.Policy;
30 import org.apache.archiva.policies.PostDownloadPolicy;
31 import org.apache.archiva.policies.PreDownloadPolicy;
32 import org.apache.archiva.redback.components.evaluator.DefaultExpressionEvaluator;
33 import org.apache.archiva.redback.components.evaluator.EvaluatorException;
34 import org.apache.archiva.redback.components.evaluator.ExpressionEvaluator;
35 import org.apache.archiva.redback.components.evaluator.sources.SystemPropertyExpressionSource;
36 import org.apache.archiva.redback.components.registry.Registry;
37 import org.apache.archiva.redback.components.registry.RegistryException;
38 import org.apache.archiva.redback.components.registry.RegistryListener;
39 import org.apache.archiva.redback.components.registry.commons.CommonsConfigurationRegistry;
40 import org.apache.archiva.redback.components.springutils.ComponentContainer;
41 import org.apache.commons.collections.CollectionUtils;
42 import org.apache.commons.collections.ListUtils;
43 import org.apache.commons.collections.MapUtils;
44 import org.apache.commons.configuration.BaseConfiguration;
45 import org.apache.commons.io.FileUtils;
46 import org.apache.commons.lang.StringUtils;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49 import org.springframework.stereotype.Service;
50
51 import javax.annotation.PostConstruct;
52 import javax.inject.Inject;
53 import javax.inject.Named;
54 import java.io.IOException;
55 import java.nio.file.Files;
56 import java.nio.file.Path;
57 import java.nio.file.Paths;
58 import java.util.ArrayList;
59 import java.util.Arrays;
60 import java.util.Collection;
61 import java.util.Collections;
62 import java.util.HashMap;
63 import java.util.HashSet;
64 import java.util.Iterator;
65 import java.util.List;
66 import java.util.Map;
67 import java.util.Map.Entry;
68 import java.util.Set;
69
70 /**
71  * <p>
72  * Implementation of configuration holder that retrieves it from the registry.
73  * </p>
74  * <p>
75  * The registry layers and merges the 2 configuration files: user, and application server.
76  * </p>
77  * <p>
78  * Instead of relying on the model defaults, if the registry is empty a default configuration file is loaded and
79  * applied from a resource. The defaults are not loaded into the registry as the lists (eg repositories) could no longer
80  * be removed if that was the case.
81  * </p>
82  * <p>
83  * When saving the configuration, it is saved to the location it was read from. If it was read from the defaults, it
84  * will be saved to the user location.
85  * However, if the configuration contains information from both sources, an exception is raised as this is currently
86  * unsupported. The reason for this is that it is not possible to identify where to re-save elements, and can result
87  * in list configurations (eg repositories) becoming inconsistent.
88  * </p>
89  * <p>
90  * If the configuration is outdated, it will be upgraded when it is loaded. This is done by checking the version flag
91  * before reading it from the registry.
92  * </p>
93  */
94 @Service("archivaConfiguration#default")
95 public class DefaultArchivaConfiguration
96     implements ArchivaConfiguration, RegistryListener
97 {
98     private Logger log = LoggerFactory.getLogger( DefaultArchivaConfiguration.class );
99
100     private static String FILE_ENCODING = "UTF-8";
101
102     /**
103      * Plexus registry to read the configuration from.
104      */
105     @Inject
106     @Named(value = "commons-configuration")
107     private Registry registry;
108
109     @Inject
110     private ComponentContainer componentContainer;
111
112     /**
113      * The configuration that has been converted.
114      */
115     private Configuration configuration;
116
117     /**
118      * see #initialize
119      *
120      * @todo these don't strictly belong in here
121      */
122     private Map<String, PreDownloadPolicy> prePolicies;
123
124     /**
125      * see #initialize
126      *
127      * @todo these don't strictly belong in here
128      */
129     private Map<String, PostDownloadPolicy> postPolicies;
130
131     /**
132      * see #initialize
133      *
134      * @todo these don't strictly belong in here
135      */
136     private Map<String, DownloadErrorPolicy> downloadErrorPolicies;
137
138
139     /**
140      * see #initialize
141      * default-value="${user.home}/.m2/archiva.xml"
142      */
143     private String userConfigFilename = "${user.home}/.m2/archiva.xml";
144
145     /**
146      * see #initialize
147      * default-value="${appserver.base}/conf/archiva.xml"
148      */
149     private String altConfigFilename = "${appserver.base}/conf/archiva.xml";
150
151     /**
152      * Configuration Listeners we've registered.
153      */
154     private Set<ConfigurationListener> listeners = new HashSet<>();
155
156     /**
157      * Registry Listeners we've registered.
158      */
159     private Set<RegistryListener> registryListeners = new HashSet<>();
160
161     /**
162      * Boolean to help determine if the configuration exists as a result of pulling in
163      * the default-archiva.xml
164      */
165     private boolean isConfigurationDefaulted = false;
166
167     private static final String KEY = "org.apache.archiva";
168
169     // Section used for default only configuration
170     private static final String KEY_DEFAULT_ONLY = "org.apache.archiva_default";
171
172     @Override
173     public Configuration getConfiguration()
174     {
175         return loadConfiguration();
176     }
177
178     private synchronized Configuration loadConfiguration()
179     {
180         if ( configuration == null )
181         {
182             configuration = load();
183             configuration = unescapeExpressions( configuration );
184             if ( isConfigurationDefaulted )
185             {
186                 configuration = checkRepositoryLocations( configuration );
187             }
188         }
189
190         return configuration;
191     }
192
193     private boolean hasConfigVersionChanged(Configuration current, Registry defaultOnlyConfiguration) {
194         return current==null || current.getVersion()==null ||
195                 !current.getVersion().trim().equals(defaultOnlyConfiguration.getString("version","").trim());
196     }
197
198     @SuppressWarnings("unchecked")
199     private Configuration load()
200     {
201         // TODO: should this be the same as section? make sure unnamed sections still work (eg, sys properties)
202         Registry subset = registry.getSubset( KEY );
203         if ( subset.getString( "version" ) == null )
204         {
205             if ( subset.getSubset( "repositoryScanning" ).isEmpty() )
206             {
207                 // only for empty
208                 subset = readDefaultConfiguration();
209             } else
210             {
211                 throw new RuntimeException( "No version tag found in configuration. Archiva configuration version 1.x is not longer supported." );
212             }
213         }
214
215         Configuration config = new ConfigurationRegistryReader().read( subset );
216         if (StringUtils.isEmpty( config.getArchivaRuntimeConfiguration().getDataDirectory() )) {
217             Path appserverBaseDir = Paths.get(registry.getString("appserver.base", ""));
218             config.getArchivaRuntimeConfiguration().setDataDirectory( appserverBaseDir.normalize().toString() );
219         }
220         if (StringUtils.isEmpty( config.getArchivaRuntimeConfiguration().getRepositoryBaseDirectory())) {
221             Path baseDir = Paths.get(config.getArchivaRuntimeConfiguration().getDataDirectory());
222             config.getArchivaRuntimeConfiguration().setRepositoryBaseDirectory( baseDir.resolve("repositories").toString() );
223         }
224
225         config.getRepositoryGroups();
226         config.getRepositoryGroupsAsMap();
227         if ( !CollectionUtils.isEmpty( config.getRemoteRepositories() ) )
228         {
229             List<RemoteRepositoryConfiguration> remoteRepos = config.getRemoteRepositories();
230             for ( RemoteRepositoryConfiguration repo : remoteRepos )
231             {
232                 // [MRM-582] Remote Repositories with empty <username> and <password> fields shouldn't be created in configuration.
233                 if ( StringUtils.isBlank( repo.getUsername() ) )
234                 {
235                     repo.setUsername( null );
236                 }
237
238                 if ( StringUtils.isBlank( repo.getPassword() ) )
239                 {
240                     repo.setPassword( null );
241                 }
242             }
243         }
244
245         if ( !config.getProxyConnectors().isEmpty() )
246         {
247             // Fix Proxy Connector Settings.
248
249             // Create a copy of the list to read from (to prevent concurrent modification exceptions)
250             List<ProxyConnectorConfiguration> proxyConnectorList = new ArrayList<>( config.getProxyConnectors() );
251             // Remove the old connector list.
252             config.getProxyConnectors().clear();
253
254             for ( ProxyConnectorConfiguration connector : proxyConnectorList )
255             {
256                 // Fix policies
257                 boolean connectorValid = true;
258
259                 Map<String, String> policies = new HashMap<>();
260                 // Make copy of policies
261                 policies.putAll( connector.getPolicies() );
262                 // Clear out policies
263                 connector.getPolicies().clear();
264
265                 // Work thru policies. cleaning them up.
266                 for ( Entry<String, String> entry : policies.entrySet() )
267                 {
268                     String policyId = entry.getKey();
269                     String setting = entry.getValue();
270
271                     // Upgrade old policy settings.
272                     if ( "releases".equals( policyId ) || "snapshots".equals( policyId ) )
273                     {
274                         if ( "ignored".equals( setting ) )
275                         {
276                             setting = AbstractUpdatePolicy.ALWAYS;
277                         }
278                         else if ( "disabled".equals( setting ) )
279                         {
280                             setting = AbstractUpdatePolicy.NEVER;
281                         }
282                     }
283                     else if ( "cache-failures".equals( policyId ) )
284                     {
285                         if ( "ignored".equals( setting ) )
286                         {
287                             setting = CachedFailuresPolicy.NO;
288                         }
289                         else if ( "cached".equals( setting ) )
290                         {
291                             setting = CachedFailuresPolicy.YES;
292                         }
293                     }
294                     else if ( "checksum".equals( policyId ) )
295                     {
296                         if ( "ignored".equals( setting ) )
297                         {
298                             setting = ChecksumPolicy.IGNORE;
299                         }
300                     }
301
302                     // Validate existance of policy key.
303                     if ( policyExists( policyId ) )
304                     {
305                         Policy policy = findPolicy( policyId );
306                         // Does option exist?
307                         if ( !policy.getOptions().contains( setting ) )
308                         {
309                             setting = policy.getDefaultOption();
310                         }
311                         connector.addPolicy( policyId, setting );
312                     }
313                     else
314                     {
315                         // Policy key doesn't exist. Don't add it to golden version.
316                         log.warn( "Policy [{}] does not exist.", policyId );
317                     }
318                 }
319
320                 if ( connectorValid )
321                 {
322                     config.addProxyConnector( connector );
323                 }
324             }
325
326             // Normalize the order fields in the proxy connectors.
327             Map<String, java.util.List<ProxyConnectorConfiguration>> proxyConnectorMap =
328                 config.getProxyConnectorAsMap();
329
330             for ( List<ProxyConnectorConfiguration> connectors : proxyConnectorMap.values() )
331             {
332                 // Sort connectors by order field.
333                 Collections.sort( connectors, ProxyConnectorConfigurationOrderComparator.getInstance() );
334
335                 // Normalize the order field values.
336                 int order = 1;
337                 for ( ProxyConnectorConfiguration connector : connectors )
338                 {
339                     connector.setOrder( order++ );
340                 }
341             }
342         }
343
344
345
346         return config;
347     }
348
349     /*
350      * Updates the checkpath list for repositories.
351      *
352      * We are replacing existing ones and adding new ones. This allows to update the list with new releases.
353      *
354      * We are also updating existing remote repositories, if they exist already.
355      *
356      * This update method should only be called, if the config version changes to avoid overwriting
357      * user repository settings all the time.
358      */
359     private void updateCheckPathDefaults(Configuration config, Registry defaultConfiguration) {
360         List<RepositoryCheckPath> existingCheckPathList = config.getArchivaDefaultConfiguration().getDefaultCheckPaths();
361         HashMap<String, RepositoryCheckPath> existingCheckPaths = new HashMap<>();
362         HashMap<String, RepositoryCheckPath> newCheckPaths = new HashMap<>();
363         for (RepositoryCheckPath path : config.getArchivaDefaultConfiguration().getDefaultCheckPaths()) {
364             existingCheckPaths.put(path.getUrl(), path);
365         }
366         List defaultCheckPathsSubsets = defaultConfiguration.getSubsetList("archivaDefaultConfiguration.defaultCheckPaths.defaultCheckPath" );
367         for ( Iterator i = defaultCheckPathsSubsets.iterator(); i.hasNext(); )
368         {
369             RepositoryCheckPath v = readRepositoryCheckPath( (Registry) i.next() );
370             if (existingCheckPaths.containsKey(v.getUrl())) {
371                 existingCheckPathList.remove(existingCheckPaths.get(v.getUrl()));
372             }
373             existingCheckPathList.add(v);
374             newCheckPaths.put(v.getUrl(), v);
375         }
376         // Remote repositories update
377         for (RemoteRepositoryConfiguration remoteRepositoryConfiguration : config.getRemoteRepositories()) {
378             String url = remoteRepositoryConfiguration.getUrl().toLowerCase();
379             if (newCheckPaths.containsKey(url)) {
380                 String currentPath = remoteRepositoryConfiguration.getCheckPath();
381                 String newPath = newCheckPaths.get(url).getPath();
382                 log.info("Updating connection check path for repository {}, from '{}' to '{}'.", remoteRepositoryConfiguration.getId(),
383                         currentPath, newPath);
384                 remoteRepositoryConfiguration.setCheckPath(newPath);
385             }
386         }
387     }
388
389     private RepositoryCheckPath readRepositoryCheckPath( Registry registry )
390     {
391         RepositoryCheckPath value = new RepositoryCheckPath();
392
393         String url = registry.getString( "url", value.getUrl() );
394
395         value.setUrl( url );
396         String path = registry.getString( "path", value.getPath() );
397         value.setPath( path );
398         return value;
399     }
400
401     private Policy findPolicy( String policyId )
402     {
403         if ( MapUtils.isEmpty( prePolicies ) )
404         {
405             log.error( "No PreDownloadPolicies found!" );
406             return null;
407         }
408
409         if ( MapUtils.isEmpty( postPolicies ) )
410         {
411             log.error( "No PostDownloadPolicies found!" );
412             return null;
413         }
414
415         Policy policy;
416
417         policy = prePolicies.get( policyId );
418         if ( policy != null )
419         {
420             return policy;
421         }
422
423         policy = postPolicies.get( policyId );
424         if ( policy != null )
425         {
426             return policy;
427         }
428
429         policy = downloadErrorPolicies.get( policyId );
430         if ( policy != null )
431         {
432             return policy;
433         }
434
435         return null;
436     }
437
438     private boolean policyExists( String policyId )
439     {
440         if ( MapUtils.isEmpty( prePolicies ) )
441         {
442             log.error( "No PreDownloadPolicies found!" );
443             return false;
444         }
445
446         if ( MapUtils.isEmpty( postPolicies ) )
447         {
448             log.error( "No PostDownloadPolicies found!" );
449             return false;
450         }
451
452         return ( prePolicies.containsKey( policyId ) || postPolicies.containsKey( policyId )
453             || downloadErrorPolicies.containsKey( policyId ) );
454     }
455
456     private Registry readDefaultConfiguration()
457     {
458         // if it contains some old configuration, remove it (Archiva 0.9)
459         registry.removeSubset( KEY );
460
461         try
462         {
463             registry.addConfigurationFromResource( "org/apache/archiva/configuration/default-archiva.xml", KEY );
464             this.isConfigurationDefaulted = true;
465         }
466         catch ( RegistryException e )
467         {
468             throw new ConfigurationRuntimeException(
469                 "Fatal error: Unable to find the built-in default configuration and load it into the registry", e );
470         }
471         return registry.getSubset( KEY );
472     }
473
474     /*
475      * Reads the default only configuration into a special prefix. This allows to check for changes
476      * of the default configuration.
477      */
478     private Registry readDefaultOnlyConfiguration()
479     {
480         registry.removeSubset(KEY_DEFAULT_ONLY);
481         try
482         {
483             registry.addConfigurationFromResource( "org/apache/archiva/configuration/default-archiva.xml", KEY_DEFAULT_ONLY);
484         }
485         catch ( RegistryException e )
486         {
487             throw new ConfigurationRuntimeException(
488                     "Fatal error: Unable to find the built-in default configuration and load it into the registry", e );
489         }
490         return registry.getSubset(KEY_DEFAULT_ONLY);
491     }
492
493     @SuppressWarnings("unchecked")
494     @Override
495     public synchronized void save( Configuration configuration )
496         throws IndeterminateConfigurationException, RegistryException
497     {
498         Registry section = registry.getSection( KEY + ".user" );
499         Registry baseSection = registry.getSection( KEY + ".base" );
500         if ( section == null )
501         {
502             section = baseSection;
503             if ( section == null )
504             {
505                 section = createDefaultConfigurationFile();
506             }
507         }
508         else if ( baseSection != null )
509         {
510             Collection<String> keys = baseSection.getKeys();
511             boolean foundList = false;
512             for ( Iterator<String> i = keys.iterator(); i.hasNext() && !foundList; )
513             {
514                 String key = i.next();
515
516                 // a little aggressive with the repositoryScanning and databaseScanning - should be no need to split
517                 // that configuration
518                 if ( key.startsWith( "repositories" ) //
519                     || key.startsWith( "proxyConnectors" ) //
520                     || key.startsWith( "networkProxies" ) //
521                     || key.startsWith( "repositoryScanning" ) //
522                     || key.startsWith( "remoteRepositories" ) //
523                     || key.startsWith( "managedRepositories" ) //
524                     || key.startsWith( "repositoryGroups" ) ) //
525                 {
526                     foundList = true;
527                 }
528             }
529
530             if ( foundList )
531             {
532                 this.configuration = null;
533
534                 throw new IndeterminateConfigurationException(
535                     "Configuration can not be saved when it is loaded from two sources" );
536             }
537         }
538
539         // escape all cron expressions to handle ','
540         escapeCronExpressions( configuration );
541
542         // [MRM-661] Due to a bug in the modello registry writer, we need to take these out by hand. They'll be put back by the writer.
543         if ( section != null )
544         {
545             if ( configuration.getManagedRepositories().isEmpty() )
546             {
547                 section.removeSubset( "managedRepositories" );
548             }
549             if ( configuration.getRemoteRepositories().isEmpty() )
550             {
551                 section.removeSubset( "remoteRepositories" );
552
553             }
554             if ( configuration.getProxyConnectors().isEmpty() )
555             {
556                 section.removeSubset( "proxyConnectors" );
557             }
558             if ( configuration.getNetworkProxies().isEmpty() )
559             {
560                 section.removeSubset( "networkProxies" );
561             }
562             if ( configuration.getLegacyArtifactPaths().isEmpty() )
563             {
564                 section.removeSubset( "legacyArtifactPaths" );
565             }
566             if ( configuration.getRepositoryGroups().isEmpty() )
567             {
568                 section.removeSubset( "repositoryGroups" );
569             }
570             if ( configuration.getRepositoryScanning() != null )
571             {
572                 if ( configuration.getRepositoryScanning().getKnownContentConsumers().isEmpty() )
573                 {
574                     section.removeSubset( "repositoryScanning.knownContentConsumers" );
575                 }
576                 if ( configuration.getRepositoryScanning().getInvalidContentConsumers().isEmpty() )
577                 {
578                     section.removeSubset( "repositoryScanning.invalidContentConsumers" );
579                 }
580             }
581             if (configuration.getArchivaRuntimeConfiguration()!=null) {
582                 section.removeSubset("archivaRuntimeConfiguration.defaultCheckPaths");
583             }
584
585             new ConfigurationRegistryWriter().write( configuration, section );
586             section.save();
587         }
588
589
590
591         this.configuration = unescapeExpressions( configuration );
592
593         triggerEvent( ConfigurationEvent.SAVED );
594     }
595
596     private void escapeCronExpressions( Configuration configuration )
597     {
598         for ( ManagedRepositoryConfiguration c : configuration.getManagedRepositories() )
599         {
600             c.setRefreshCronExpression( escapeCronExpression( c.getRefreshCronExpression() ) );
601         }
602     }
603
604     private Registry createDefaultConfigurationFile()
605         throws RegistryException
606     {
607         // TODO: may not be needed under commons-configuration 1.4 - check
608
609         String contents = "<configuration />";
610
611         String fileLocation = userConfigFilename;
612
613         if ( !writeFile( "user configuration", userConfigFilename, contents ) )
614         {
615             fileLocation = altConfigFilename;
616             if ( !writeFile( "alternative configuration", altConfigFilename, contents ) )
617             {
618                 throw new RegistryException(
619                     "Unable to create configuration file in either user [" + userConfigFilename + "] or alternative ["
620                         + altConfigFilename
621                         + "] locations on disk, usually happens when not allowed to write to those locations." );
622             }
623         }
624
625         // olamy hackish I know :-)
626         contents = "<configuration><xml fileName=\"" + fileLocation
627             + "\" config-forceCreate=\"true\" config-name=\"org.apache.archiva.user\"/>" + "</configuration>";
628
629         ( (CommonsConfigurationRegistry) registry ).setProperties( contents );
630
631         registry.initialize();
632
633         for ( RegistryListener regListener : registryListeners )
634         {
635             addRegistryChangeListener( regListener );
636         }
637
638         triggerEvent( ConfigurationEvent.SAVED );
639
640         Registry section = registry.getSection( KEY + ".user" );
641         return section == null ? new CommonsConfigurationRegistry( new BaseConfiguration() ) : section;
642     }
643
644     /**
645      * Attempts to write the contents to a file, if an IOException occurs, return false.
646      * <p/>
647      * The file will be created if the directory to the file exists, otherwise this will return false.
648      *
649      * @param filetype the filetype (freeform text) to use in logging messages when failure to write.
650      * @param path     the path to write to.
651      * @param contents the contents to write.
652      * @return true if write successful.
653      */
654     private boolean writeFile( String filetype, String path, String contents )
655     {
656         Path file = Paths.get( path );
657
658         try
659         {
660             // Check parent directory (if it is declared)
661             if ( file.getParent() != null )
662             {
663                 // Check that directory exists
664                 if ( !Files.isDirectory( file.getParent() ) )
665                 {
666                     // Directory to file must exist for file to be created
667                     return false;
668                 }
669             }
670             FileUtils.writeStringToFile( file.toFile(), contents, FILE_ENCODING);
671             return true;
672         }
673         catch ( IOException e )
674         {
675             log.error( "Unable to create {} file: {}", filetype, e.getMessage(), e );
676             return false;
677         }
678     }
679
680     private void triggerEvent( int type )
681     {
682         ConfigurationEvent evt = new ConfigurationEvent( type );
683         for ( ConfigurationListener listener : listeners )
684         {
685             listener.configurationEvent( evt );
686         }
687     }
688
689     @Override
690     public void addListener( ConfigurationListener listener )
691     {
692         if ( listener == null )
693         {
694             return;
695         }
696
697         listeners.add( listener );
698     }
699
700     @Override
701     public void removeListener( ConfigurationListener listener )
702     {
703         if ( listener == null )
704         {
705             return;
706         }
707
708         listeners.remove( listener );
709     }
710
711
712     @Override
713     public void addChangeListener( RegistryListener listener )
714     {
715         addRegistryChangeListener( listener );
716
717         // keep track for later
718         registryListeners.add( listener );
719     }
720
721     private void addRegistryChangeListener( RegistryListener listener )
722     {
723         Registry section = registry.getSection( KEY + ".user" );
724         if ( section != null )
725         {
726             section.addChangeListener( listener );
727         }
728         section = registry.getSection( KEY + ".base" );
729         if ( section != null )
730         {
731             section.addChangeListener( listener );
732         }
733     }
734
735     @Override
736     public void removeChangeListener( RegistryListener listener )
737     {
738         boolean removed = registryListeners.remove( listener );
739         log.debug( "RegistryListener: '{}' removed {}", listener, removed );
740
741         Registry section = registry.getSection( KEY + ".user" );
742         if ( section != null )
743         {
744             section.removeChangeListener( listener );
745         }
746         section = registry.getSection( KEY + ".base" );
747         if ( section != null )
748         {
749             section.removeChangeListener( listener );
750         }
751
752     }
753
754     @PostConstruct
755     public void initialize()
756     {
757
758         this.postPolicies = componentContainer.buildMapWithRole( PostDownloadPolicy.class );
759         this.prePolicies = componentContainer.buildMapWithRole( PreDownloadPolicy.class );
760         this.downloadErrorPolicies = componentContainer.buildMapWithRole( DownloadErrorPolicy.class );
761         // Resolve expressions in the userConfigFilename and altConfigFilename
762         try
763         {
764             ExpressionEvaluator expressionEvaluator = new DefaultExpressionEvaluator();
765             expressionEvaluator.addExpressionSource( new SystemPropertyExpressionSource() );
766             String userConfigFileNameSysProps = System.getProperty( "archiva.user.configFileName" );
767             if ( StringUtils.isNotBlank( userConfigFileNameSysProps ) )
768             {
769                 userConfigFilename = userConfigFileNameSysProps;
770             }
771             else
772             {
773                 userConfigFilename = expressionEvaluator.expand( userConfigFilename );
774             }
775             altConfigFilename = expressionEvaluator.expand( altConfigFilename );
776             loadConfiguration();
777             handleUpgradeConfiguration();
778         }
779         catch ( IndeterminateConfigurationException | RegistryException e )
780         {
781             throw new RuntimeException( "failed during upgrade from previous version" + e.getMessage(), e );
782         }
783         catch ( EvaluatorException e )
784         {
785             throw new RuntimeException(
786                 "Unable to evaluate expressions found in " + "userConfigFilename or altConfigFilename.", e );
787         }
788         registry.addChangeListener( this );
789     }
790
791     /**
792      * Handle upgrade to newer version
793      */
794     private void handleUpgradeConfiguration()
795         throws RegistryException, IndeterminateConfigurationException
796     {
797
798         List<String> dbConsumers = Arrays.asList( "update-db-artifact", "update-db-repository-metadata" );
799
800         // remove database consumers if here
801         List<String> intersec =
802             ListUtils.intersection( dbConsumers, configuration.getRepositoryScanning().getKnownContentConsumers() );
803
804         if ( !intersec.isEmpty() )
805         {
806
807             List<String> knowContentConsumers =
808                 new ArrayList<>( configuration.getRepositoryScanning().getKnownContentConsumers().size() );
809             for ( String knowContentConsumer : configuration.getRepositoryScanning().getKnownContentConsumers() )
810             {
811                 if ( !dbConsumers.contains( knowContentConsumer ) )
812                 {
813                     knowContentConsumers.add( knowContentConsumer );
814                 }
815             }
816
817             configuration.getRepositoryScanning().setKnownContentConsumers( knowContentConsumers );
818         }
819
820         // ensure create-archiva-metadata is here
821         if ( !configuration.getRepositoryScanning().getKnownContentConsumers().contains( "create-archiva-metadata" ) )
822         {
823             List<String> knowContentConsumers =
824                 new ArrayList<>( configuration.getRepositoryScanning().getKnownContentConsumers() );
825             knowContentConsumers.add( "create-archiva-metadata" );
826             configuration.getRepositoryScanning().setKnownContentConsumers( knowContentConsumers );
827         }
828
829         // ensure duplicate-artifacts is here
830         if ( !configuration.getRepositoryScanning().getKnownContentConsumers().contains( "duplicate-artifacts" ) )
831         {
832             List<String> knowContentConsumers =
833                 new ArrayList<>( configuration.getRepositoryScanning().getKnownContentConsumers() );
834             knowContentConsumers.add( "duplicate-artifacts" );
835             configuration.getRepositoryScanning().setKnownContentConsumers( knowContentConsumers );
836         }
837
838         Registry defaultOnlyConfiguration = readDefaultOnlyConfiguration();
839         // Currently we check only for configuration version change, not certain version numbers.
840         if (hasConfigVersionChanged(configuration, defaultOnlyConfiguration)) {
841             updateCheckPathDefaults(configuration, defaultOnlyConfiguration);
842             String newVersion = defaultOnlyConfiguration.getString("version");
843             if (newVersion==null) {
844                 throw new IndeterminateConfigurationException("The default configuration has no version information!");
845             }
846             configuration.setVersion(newVersion);
847             try {
848                 save(configuration);
849             } catch (IndeterminateConfigurationException e) {
850                 log.error("Error occured during configuration update to new version: {}", e.getMessage());
851             } catch (RegistryException e) {
852                 log.error("Error occured during configuration update to new version: {}", e.getMessage());
853             }
854         }
855     }
856
857     @Override
858     public void reload()
859     {
860         this.configuration = null;
861         try
862         {
863             this.registry.initialize();
864         }
865         catch ( RegistryException e )
866         {
867             throw new ConfigurationRuntimeException( e.getMessage(), e );
868         }
869         this.initialize();
870     }
871
872     @Override
873     public void beforeConfigurationChange( Registry registry, String propertyName, Object propertyValue )
874     {
875         // nothing to do here
876     }
877
878     @Override
879     public synchronized void afterConfigurationChange( Registry registry, String propertyName, Object propertyValue )
880     {
881         configuration = null;
882     }
883
884     private String removeExpressions( String directory )
885     {
886         String value = StringUtils.replace( directory, "${appserver.base}",
887                                             registry.getString( "appserver.base", "${appserver.base}" ) );
888         value = StringUtils.replace( value, "${appserver.home}",
889                                      registry.getString( "appserver.home", "${appserver.home}" ) );
890         return value;
891     }
892
893     private String unescapeCronExpression( String cronExpression )
894     {
895         return StringUtils.replace( cronExpression, "\\,", "," );
896     }
897
898     private String escapeCronExpression( String cronExpression )
899     {
900         return StringUtils.replace( cronExpression, ",", "\\," );
901     }
902
903     private Configuration unescapeExpressions( Configuration config )
904     {
905         // TODO: for commons-configuration 1.3 only
906         for ( ManagedRepositoryConfiguration c : config.getManagedRepositories() )
907         {
908             c.setLocation( removeExpressions( c.getLocation() ) );
909             c.setRefreshCronExpression( unescapeCronExpression( c.getRefreshCronExpression() ) );
910         }
911
912         return config;
913     }
914
915     private Configuration checkRepositoryLocations( Configuration config )
916     {
917         // additional check for [MRM-789], ensure that the location of the default repositories 
918         // are not installed in the server installation        
919         for ( ManagedRepositoryConfiguration repo : (List<ManagedRepositoryConfiguration>) config.getManagedRepositories() )
920         {
921             String repoPath = repo.getLocation();
922             Path repoLocation = Paths.get( repoPath );
923
924             if ( Files.exists(repoLocation) && Files.isDirectory(repoLocation) && !repoPath.endsWith(
925                 "data/repositories/" + repo.getId() ) )
926             {
927                 repo.setLocation( repoPath + "/data/repositories/" + repo.getId() );
928             }
929         }
930
931         return config;
932     }
933
934     public String getUserConfigFilename()
935     {
936         return userConfigFilename;
937     }
938
939     public String getAltConfigFilename()
940     {
941         return altConfigFilename;
942     }
943
944     @Override
945     public boolean isDefaulted()
946     {
947         return this.isConfigurationDefaulted;
948     }
949
950     public Registry getRegistry()
951     {
952         return registry;
953     }
954
955     public void setRegistry( Registry registry )
956     {
957         this.registry = registry;
958     }
959
960
961     public void setUserConfigFilename( String userConfigFilename )
962     {
963         this.userConfigFilename = userConfigFilename;
964     }
965
966     public void setAltConfigFilename( String altConfigFilename )
967     {
968         this.altConfigFilename = altConfigFilename;
969     }
970 }