]> source.dussan.org Git - archiva.git/blob
11ae70569aa959f88d54cebe0b66b7a14e20ccc8
[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)
414             throws IndeterminateConfigurationException, RegistryException {
415         Registry section = registry.getSection(KEY + ".user");
416         Registry baseSection = registry.getSection(KEY + ".base");
417         if (section == null) {
418             section = baseSection;
419             if (section == null) {
420                 section = createDefaultConfigurationFile();
421             }
422         } else if (baseSection != null) {
423             Collection<String> keys = baseSection.getKeys();
424             boolean foundList = false;
425             for (Iterator<String> i = keys.iterator(); i.hasNext() && !foundList; ) {
426                 String key = i.next();
427
428                 // a little aggressive with the repositoryScanning and databaseScanning - should be no need to split
429                 // that configuration
430                 if (key.startsWith("repositories") //
431                         || key.startsWith("proxyConnectors") //
432                         || key.startsWith("networkProxies") //
433                         || key.startsWith("repositoryScanning") //
434                         || key.startsWith("remoteRepositories") //
435                         || key.startsWith("managedRepositories") //
436                         || key.startsWith("repositoryGroups")) //
437                 {
438                     foundList = true;
439                 }
440             }
441
442             if (foundList) {
443                 this.configuration = null;
444
445                 throw new IndeterminateConfigurationException(
446                         "Configuration can not be saved when it is loaded from two sources");
447             }
448         }
449
450         // escape all cron expressions to handle ','
451         escapeCronExpressions(configuration);
452
453         // [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.
454         if (section != null) {
455             if (configuration.getManagedRepositories().isEmpty()) {
456                 section.removeSubset("managedRepositories");
457             }
458             if (configuration.getRemoteRepositories().isEmpty()) {
459                 section.removeSubset("remoteRepositories");
460
461             }
462             if (configuration.getProxyConnectors().isEmpty()) {
463                 section.removeSubset("proxyConnectors");
464             }
465             if (configuration.getNetworkProxies().isEmpty()) {
466                 section.removeSubset("networkProxies");
467             }
468             if (configuration.getLegacyArtifactPaths().isEmpty()) {
469                 section.removeSubset("legacyArtifactPaths");
470             }
471             if (configuration.getRepositoryGroups().isEmpty()) {
472                 section.removeSubset("repositoryGroups");
473             }
474             if (configuration.getRepositoryScanning() != null) {
475                 if (configuration.getRepositoryScanning().getKnownContentConsumers().isEmpty()) {
476                     section.removeSubset("repositoryScanning.knownContentConsumers");
477                 }
478                 if (configuration.getRepositoryScanning().getInvalidContentConsumers().isEmpty()) {
479                     section.removeSubset("repositoryScanning.invalidContentConsumers");
480                 }
481             }
482             if (configuration.getArchivaRuntimeConfiguration() != null) {
483                 section.removeSubset("archivaRuntimeConfiguration.defaultCheckPaths");
484             }
485
486             new ConfigurationRegistryWriter().write(configuration, section);
487             section.save();
488         }
489
490
491         this.configuration = unescapeExpressions(configuration);
492         isConfigurationDefaulted = false;
493
494         triggerEvent(ConfigurationEvent.SAVED);
495     }
496
497     private void escapeCronExpressions(Configuration configuration) {
498         for (ManagedRepositoryConfiguration c : configuration.getManagedRepositories()) {
499             c.setRefreshCronExpression(escapeCronExpression(c.getRefreshCronExpression()));
500         }
501     }
502
503     private Registry createDefaultConfigurationFile()
504             throws RegistryException {
505         // TODO: may not be needed under commons-configuration 1.4 - check
506
507         String contents = "<configuration />";
508
509         String fileLocation = userConfigFilename;
510
511         if (!writeFile("user configuration", userConfigFilename, contents)) {
512             fileLocation = altConfigFilename;
513             if (!writeFile("alternative configuration", altConfigFilename, contents, true)) {
514                 throw new RegistryException(
515                         "Unable to create configuration file in either user [" + userConfigFilename + "] or alternative ["
516                                 + altConfigFilename
517                                 + "] locations on disk, usually happens when not allowed to write to those locations.");
518             }
519         }
520
521         // olamy hackish I know :-)
522         contents = "<configuration><xml fileName=\"" + fileLocation
523                 + "\" config-forceCreate=\"true\" config-name=\"org.apache.archiva.user\"/>" + "</configuration>";
524
525         ((CommonsConfigurationRegistry) registry).setInitialConfiguration(contents);
526
527         registry.initialize();
528
529         for (RegistryListener regListener : registryListeners) {
530             addRegistryChangeListener(regListener);
531         }
532
533         triggerEvent(ConfigurationEvent.SAVED);
534
535         Registry section = registry.getSection(KEY + ".user");
536         if (section == null) {
537             return new CommonsConfigurationRegistry( );
538         } else {
539             return section;
540         }
541     }
542
543     private boolean writeFile(String filetype, String path, String contents) {
544         return writeFile( filetype, path, contents, false );
545     }
546
547     /**
548      * Attempts to write the contents to a file, if an IOException occurs, return false.
549      * <p/>
550      * The file will be created if the directory to the file exists, otherwise this will return false.
551      *
552      * @param filetype the filetype (freeform text) to use in logging messages when failure to write.
553      * @param path     the path to write to.
554      * @param contents the contents to write.
555      * @return true if write successful.
556      */
557     private boolean writeFile(String filetype, String path, String contents, boolean createDirs) {                
558         try {
559             Path file = Paths.get(path);
560             // Check parent directory (if it is declared)
561             final Path parent = file.getParent();
562             if (parent != null) {
563                 // Check that directory exists
564                 if (!Files.exists( parent ) && createDirs) {
565                     Files.createDirectories( parent );
566                 }
567                 if (!Files.isDirectory(parent)) {
568                     // Directory to file must exist for file to be created
569                     return false;
570                 }
571             }
572             FileUtils.writeStringToFile(file.toFile(), contents, FILE_ENCODING);
573             return true;
574         } catch (IOException e) {
575             log.error("Unable to create {} file: {}", filetype, e.getMessage(), e);
576             return false;
577         } catch (InvalidPathException ipe) {
578             log.error("Unable to read {} file: {}", path, ipe.getMessage(), ipe);
579             return false;
580         }
581     }
582
583     private void triggerEvent(int type) {
584         ConfigurationEvent evt = new ConfigurationEvent(type);
585         for (ConfigurationListener listener : listeners) {
586             listener.configurationEvent(evt);
587         }
588     }
589
590     @Override
591     public void addListener(ConfigurationListener listener) {
592         if (listener == null) {
593             return;
594         }
595
596         listeners.add(listener);
597     }
598
599     @Override
600     public void removeListener(ConfigurationListener listener) {
601         if (listener == null) {
602             return;
603         }
604
605         listeners.remove(listener);
606     }
607
608
609     @Override
610     public void addChangeListener(RegistryListener listener) {
611         addRegistryChangeListener(listener);
612
613         // keep track for later
614         registryListeners.add(listener);
615     }
616
617     private void addRegistryChangeListener(RegistryListener listener) {
618         Registry section = registry.getSection(KEY + ".user");
619         if (section != null) {
620             section.addChangeListener(listener);
621         }
622         section = registry.getSection(KEY + ".base");
623         if (section != null) {
624             section.addChangeListener(listener);
625         }
626     }
627
628     @Override
629     public void removeChangeListener(RegistryListener listener) {
630         boolean removed = registryListeners.remove(listener);
631         log.debug("RegistryListener: '{}' removed {}", listener, removed);
632
633         Registry section = registry.getSection(KEY + ".user");
634         if (section != null) {
635             section.removeChangeListener(listener);
636         }
637         section = registry.getSection(KEY + ".base");
638         if (section != null) {
639             section.removeChangeListener(listener);
640         }
641
642     }
643
644     @PostConstruct
645     public void initialize() {
646
647         // Resolve expressions in the userConfigFilename and altConfigFilename
648         try {
649             ExpressionEvaluator expressionEvaluator = new DefaultExpressionEvaluator();
650             expressionEvaluator.addExpressionSource(new SystemPropertyExpressionSource());
651             String userConfigFileNameSysProps = System.getProperty(USER_CONFIG_PROPERTY);
652             if (StringUtils.isNotBlank(userConfigFileNameSysProps)) {
653                 userConfigFilename = userConfigFileNameSysProps;
654             } else {
655                 String userConfigFileNameEnv = System.getenv(USER_CONFIG_ENVVAR);
656                 if (StringUtils.isNotBlank(userConfigFileNameEnv)) {
657                     userConfigFilename = userConfigFileNameEnv;
658                 } else {
659                     userConfigFilename = expressionEvaluator.expand(userConfigFilename);
660                 }
661             }
662             altConfigFilename = expressionEvaluator.expand(altConfigFilename);
663             loadConfiguration();
664             handleUpgradeConfiguration();
665         } catch (IndeterminateConfigurationException | RegistryException e) {
666             throw new RuntimeException("failed during upgrade from previous version" + e.getMessage(), e);
667         } catch (EvaluatorException e) {
668             throw new RuntimeException(
669                     "Unable to evaluate expressions found in " + "userConfigFilename or altConfigFilename.", e);
670         }
671         registry.addChangeListener(this);
672     }
673
674     /**
675      * Handle upgrade to newer version
676      */
677     private void handleUpgradeConfiguration()
678             throws RegistryException, IndeterminateConfigurationException {
679
680         List<String> dbConsumers = Arrays.asList("update-db-artifact", "update-db-repository-metadata");
681
682         // remove database consumers if here
683         List<String> intersec =
684                 ListUtils.intersection(dbConsumers, configuration.getRepositoryScanning().getKnownContentConsumers());
685
686         if (!intersec.isEmpty()) {
687
688             List<String> knowContentConsumers =
689                     new ArrayList<>(configuration.getRepositoryScanning().getKnownContentConsumers().size());
690             for (String knowContentConsumer : configuration.getRepositoryScanning().getKnownContentConsumers()) {
691                 if (!dbConsumers.contains(knowContentConsumer)) {
692                     knowContentConsumers.add(knowContentConsumer);
693                 }
694             }
695
696             configuration.getRepositoryScanning().setKnownContentConsumers(knowContentConsumers);
697         }
698
699         // ensure create-archiva-metadata is here
700         if (!configuration.getRepositoryScanning().getKnownContentConsumers().contains("create-archiva-metadata")) {
701             List<String> knowContentConsumers =
702                     new ArrayList<>(configuration.getRepositoryScanning().getKnownContentConsumers());
703             knowContentConsumers.add("create-archiva-metadata");
704             configuration.getRepositoryScanning().setKnownContentConsumers(knowContentConsumers);
705         }
706
707         // ensure duplicate-artifacts is here
708         if (!configuration.getRepositoryScanning().getKnownContentConsumers().contains("duplicate-artifacts")) {
709             List<String> knowContentConsumers =
710                     new ArrayList<>(configuration.getRepositoryScanning().getKnownContentConsumers());
711             knowContentConsumers.add("duplicate-artifacts");
712             configuration.getRepositoryScanning().setKnownContentConsumers(knowContentConsumers);
713         }
714
715         Registry defaultOnlyConfiguration = readDefaultOnlyConfiguration();
716         // Currently we check only for configuration version change, not certain version numbers.
717         if (hasConfigVersionChanged(configuration, defaultOnlyConfiguration)) {
718             updateCheckPathDefaults(configuration, defaultOnlyConfiguration);
719             String newVersion = defaultOnlyConfiguration.getString("version");
720             if (newVersion == null) {
721                 throw new IndeterminateConfigurationException("The default configuration has no version information!");
722             }
723             configuration.setVersion(newVersion);
724             try {
725                 save(configuration);
726             } catch (IndeterminateConfigurationException e) {
727                 log.error("Error occured during configuration update to new version: {}", e.getMessage());
728             } catch (RegistryException e) {
729                 log.error("Error occured during configuration update to new version: {}", e.getMessage());
730             }
731         }
732     }
733
734     @Override
735     public void reload() {
736         this.configuration = null;
737         try {
738             this.registry.initialize();
739         } catch (RegistryException e) {
740             throw new ConfigurationRuntimeException(e.getMessage(), e);
741         }
742         this.initialize();
743     }
744
745     @Override
746     public Locale getDefaultLocale() {
747         return defaultLocale;
748     }
749
750     @Override
751     public List<Locale.LanguageRange> getLanguagePriorities() {
752         return languagePriorities;
753     }
754
755     @Override
756     public Path getAppServerBaseDir() {
757         String basePath = registry.getString("appserver.base");
758         if (!StringUtils.isEmpty(basePath)) {
759             return Paths.get(basePath);
760         } else {
761             return Paths.get("");
762         }
763     }
764
765     @Override
766     public Path getRepositoryBaseDir() {
767         if (repositoryBaseDirectory == null) {
768             getConfiguration();
769         }
770         return repositoryBaseDirectory;
771
772     }
773
774     @Override
775     public Path getRemoteRepositoryBaseDir() {
776         if (remoteRepositoryBaseDirectory == null) {
777             getConfiguration();
778         }
779         return remoteRepositoryBaseDirectory;
780     }
781
782     @Override
783     public Path getRepositoryGroupBaseDir() {
784         if (repositoryGroupBaseDirectory == null) {
785             getConfiguration();
786         }
787         return repositoryGroupBaseDirectory;
788     }
789
790     @Override
791     public Path getDataDirectory() {
792         if (dataDirectory == null) {
793             getConfiguration();
794         }
795         return dataDirectory;
796     }
797
798     @Override
799     public void beforeConfigurationChange(Registry registry, String propertyName, Object propertyValue) {
800         // nothing to do here
801     }
802
803     @Override
804     public synchronized void afterConfigurationChange(Registry registry, String propertyName, Object propertyValue) {
805         // configuration = null;
806         // this.dataDirectory = null;
807         // this.repositoryBaseDirectory = null;
808     }
809
810     private String removeExpressions(String directory) {
811         String value = StringUtils.replace(directory, "${appserver.base}",
812                 registry.getString("appserver.base", "${appserver.base}"));
813         value = StringUtils.replace(value, "${appserver.home}",
814                 registry.getString("appserver.home", "${appserver.home}"));
815         return value;
816     }
817
818     private String unescapeCronExpression(String cronExpression) {
819         return StringUtils.replace(cronExpression, "\\,", ",");
820     }
821
822     private String escapeCronExpression(String cronExpression) {
823         return StringUtils.replace(cronExpression, ",", "\\,");
824     }
825
826     private Configuration unescapeExpressions(Configuration config) {
827         // TODO: for commons-configuration 1.3 only
828         for (ManagedRepositoryConfiguration c : config.getManagedRepositories()) {
829             c.setLocation(removeExpressions(c.getLocation()));
830             c.setRefreshCronExpression(unescapeCronExpression(c.getRefreshCronExpression()));
831         }
832
833         return config;
834     }
835
836     private Configuration checkRepositoryLocations(Configuration config) {
837         // additional check for [MRM-789], ensure that the location of the default repositories 
838         // are not installed in the server installation        
839         for (ManagedRepositoryConfiguration repo : (List<ManagedRepositoryConfiguration>) config.getManagedRepositories()) {
840             String repoPath = repo.getLocation();
841             Path repoLocation = Paths.get(repoPath);
842
843             if (Files.exists(repoLocation) && Files.isDirectory(repoLocation) && !repoPath.endsWith(
844                     "data/repositories/" + repo.getId())) {
845                 repo.setLocation(repoPath + "/data/repositories/" + repo.getId());
846             }
847         }
848
849         return config;
850     }
851
852     public String getUserConfigFilename() {
853         return userConfigFilename;
854     }
855
856     public String getAltConfigFilename() {
857         return altConfigFilename;
858     }
859
860     @Override
861     public boolean isDefaulted() {
862         return this.isConfigurationDefaulted;
863     }
864
865     public Registry getRegistry() {
866         return registry;
867     }
868
869     public void setRegistry(Registry registry) {
870         this.registry = registry;
871     }
872
873
874     public void setUserConfigFilename(String userConfigFilename) {
875         this.userConfigFilename = userConfigFilename;
876     }
877
878     public void setAltConfigFilename(String altConfigFilename) {
879         this.altConfigFilename = altConfigFilename;
880     }
881 }