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