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