This project has retired. For details please refer to its Attic page.
Source code
001package org.apache.archiva.configuration;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import org.apache.archiva.configuration.functors.ProxyConnectorConfigurationOrderComparator;
023import org.apache.archiva.configuration.io.registry.ConfigurationRegistryReader;
024import org.apache.archiva.configuration.io.registry.ConfigurationRegistryWriter;
025import org.apache.archiva.policies.AbstractUpdatePolicy;
026import org.apache.archiva.policies.CachedFailuresPolicy;
027import org.apache.archiva.policies.ChecksumPolicy;
028import org.apache.archiva.components.evaluator.DefaultExpressionEvaluator;
029import org.apache.archiva.components.evaluator.EvaluatorException;
030import org.apache.archiva.components.evaluator.ExpressionEvaluator;
031import org.apache.archiva.components.evaluator.sources.SystemPropertyExpressionSource;
032import org.apache.archiva.components.registry.Registry;
033import org.apache.archiva.components.registry.RegistryException;
034import org.apache.archiva.components.registry.RegistryListener;
035import org.apache.archiva.components.registry.commons.CommonsConfigurationRegistry;
036import org.apache.commons.collections4.CollectionUtils;
037import org.apache.commons.collections4.ListUtils;
038import org.apache.commons.configuration.BaseConfiguration;
039import org.apache.commons.io.FileUtils;
040import org.apache.commons.lang3.StringUtils;
041import org.slf4j.Logger;
042import org.slf4j.LoggerFactory;
043import org.springframework.stereotype.Service;
044
045import javax.annotation.PostConstruct;
046import javax.inject.Inject;
047import javax.inject.Named;
048import java.io.IOException;
049import java.nio.file.Files;
050import java.nio.file.Path;
051import java.nio.file.Paths;
052import java.util.*;
053import java.util.Map.Entry;
054
055/**
056 * <p>
057 * Implementation of configuration holder that retrieves it from the registry.
058 * </p>
059 * <p>
060 * The registry layers and merges the 2 configuration files: user, and application server.
061 * </p>
062 * <p>
063 * Instead of relying on the model defaults, if the registry is empty a default configuration file is loaded and
064 * applied from a resource. The defaults are not loaded into the registry as the lists (eg repositories) could no longer
065 * be removed if that was the case.
066 * </p>
067 * <p>
068 * When saving the configuration, it is saved to the location it was read from. If it was read from the defaults, it
069 * will be saved to the user location.
070 * However, if the configuration contains information from both sources, an exception is raised as this is currently
071 * unsupported. The reason for this is that it is not possible to identify where to re-save elements, and can result
072 * in list configurations (eg repositories) becoming inconsistent.
073 * </p>
074 * <p>
075 * If the configuration is outdated, it will be upgraded when it is loaded. This is done by checking the version flag
076 * before reading it from the registry.
077 * <p>
078 * FIXME: The synchronization must be improved, the current impl may lead to inconsistent data or multiple getConfiguration() calls (martin_s@apache.org)
079 * </p>
080 */
081@Service("archivaConfiguration#default")
082public class DefaultArchivaConfiguration
083        implements ArchivaConfiguration, RegistryListener {
084    private final Logger log = LoggerFactory.getLogger(DefaultArchivaConfiguration.class);
085
086    private static String FILE_ENCODING = "UTF-8";
087
088    /**
089     * Plexus registry to read the configuration from.
090     */
091    @Inject
092    @Named(value = "commons-configuration")
093    private Registry registry;
094
095    /**
096     * The configuration that has been converted.
097     */
098    private Configuration configuration;
099
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).setProperties(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        return section == null ? new CommonsConfigurationRegistry(new BaseConfiguration()) : section;
537    }
538
539    private boolean writeFile(String filetype, String path, String contents) {
540        return writeFile( filetype, path, contents, false );
541    }
542
543    /**
544     * Attempts to write the contents to a file, if an IOException occurs, return false.
545     * <p/>
546     * The file will be created if the directory to the file exists, otherwise this will return false.
547     *
548     * @param filetype the filetype (freeform text) to use in logging messages when failure to write.
549     * @param path     the path to write to.
550     * @param contents the contents to write.
551     * @return true if write successful.
552     */
553    private boolean writeFile(String filetype, String path, String contents, boolean createDirs) {
554        Path file = Paths.get(path);
555
556        try {
557            // Check parent directory (if it is declared)
558            final Path parent = file.getParent();
559            if (parent != null) {
560                // Check that directory exists
561                if (!Files.exists( parent ) && createDirs) {
562                    Files.createDirectories( parent );
563                }
564                if (!Files.isDirectory(parent)) {
565                    // Directory to file must exist for file to be created
566                    return false;
567                }
568            }
569            FileUtils.writeStringToFile(file.toFile(), contents, FILE_ENCODING);
570            return true;
571        } catch (IOException e) {
572            log.error("Unable to create {} file: {}", filetype, e.getMessage(), e);
573            return false;
574        }
575    }
576
577    private void triggerEvent(int type) {
578        ConfigurationEvent evt = new ConfigurationEvent(type);
579        for (ConfigurationListener listener : listeners) {
580            listener.configurationEvent(evt);
581        }
582    }
583
584    @Override
585    public void addListener(ConfigurationListener listener) {
586        if (listener == null) {
587            return;
588        }
589
590        listeners.add(listener);
591    }
592
593    @Override
594    public void removeListener(ConfigurationListener listener) {
595        if (listener == null) {
596            return;
597        }
598
599        listeners.remove(listener);
600    }
601
602
603    @Override
604    public void addChangeListener(RegistryListener listener) {
605        addRegistryChangeListener(listener);
606
607        // keep track for later
608        registryListeners.add(listener);
609    }
610
611    private void addRegistryChangeListener(RegistryListener listener) {
612        Registry section = registry.getSection(KEY + ".user");
613        if (section != null) {
614            section.addChangeListener(listener);
615        }
616        section = registry.getSection(KEY + ".base");
617        if (section != null) {
618            section.addChangeListener(listener);
619        }
620    }
621
622    @Override
623    public void removeChangeListener(RegistryListener listener) {
624        boolean removed = registryListeners.remove(listener);
625        log.debug("RegistryListener: '{}' removed {}", listener, removed);
626
627        Registry section = registry.getSection(KEY + ".user");
628        if (section != null) {
629            section.removeChangeListener(listener);
630        }
631        section = registry.getSection(KEY + ".base");
632        if (section != null) {
633            section.removeChangeListener(listener);
634        }
635
636    }
637
638    @PostConstruct
639    public void initialize() {
640
641        // Resolve expressions in the userConfigFilename and altConfigFilename
642        try {
643            ExpressionEvaluator expressionEvaluator = new DefaultExpressionEvaluator();
644            expressionEvaluator.addExpressionSource(new SystemPropertyExpressionSource());
645            String userConfigFileNameSysProps = System.getProperty(USER_CONFIG_PROPERTY);
646            if (StringUtils.isNotBlank(userConfigFileNameSysProps)) {
647                userConfigFilename = userConfigFileNameSysProps;
648            } else {
649                String userConfigFileNameEnv = System.getenv(USER_CONFIG_ENVVAR);
650                if (StringUtils.isNotBlank(userConfigFileNameEnv)) {
651                    userConfigFilename = userConfigFileNameEnv;
652                } else {
653                    userConfigFilename = expressionEvaluator.expand(userConfigFilename);
654                }
655            }
656            altConfigFilename = expressionEvaluator.expand(altConfigFilename);
657            loadConfiguration();
658            handleUpgradeConfiguration();
659        } catch (IndeterminateConfigurationException | RegistryException e) {
660            throw new RuntimeException("failed during upgrade from previous version" + e.getMessage(), e);
661        } catch (EvaluatorException e) {
662            throw new RuntimeException(
663                    "Unable to evaluate expressions found in " + "userConfigFilename or altConfigFilename.", e);
664        }
665        registry.addChangeListener(this);
666    }
667
668    /**
669     * Handle upgrade to newer version
670     */
671    private void handleUpgradeConfiguration()
672            throws RegistryException, IndeterminateConfigurationException {
673
674        List<String> dbConsumers = Arrays.asList("update-db-artifact", "update-db-repository-metadata");
675
676        // remove database consumers if here
677        List<String> intersec =
678                ListUtils.intersection(dbConsumers, configuration.getRepositoryScanning().getKnownContentConsumers());
679
680        if (!intersec.isEmpty()) {
681
682            List<String> knowContentConsumers =
683                    new ArrayList<>(configuration.getRepositoryScanning().getKnownContentConsumers().size());
684            for (String knowContentConsumer : configuration.getRepositoryScanning().getKnownContentConsumers()) {
685                if (!dbConsumers.contains(knowContentConsumer)) {
686                    knowContentConsumers.add(knowContentConsumer);
687                }
688            }
689
690            configuration.getRepositoryScanning().setKnownContentConsumers(knowContentConsumers);
691        }
692
693        // ensure create-archiva-metadata is here
694        if (!configuration.getRepositoryScanning().getKnownContentConsumers().contains("create-archiva-metadata")) {
695            List<String> knowContentConsumers =
696                    new ArrayList<>(configuration.getRepositoryScanning().getKnownContentConsumers());
697            knowContentConsumers.add("create-archiva-metadata");
698            configuration.getRepositoryScanning().setKnownContentConsumers(knowContentConsumers);
699        }
700
701        // ensure duplicate-artifacts is here
702        if (!configuration.getRepositoryScanning().getKnownContentConsumers().contains("duplicate-artifacts")) {
703            List<String> knowContentConsumers =
704                    new ArrayList<>(configuration.getRepositoryScanning().getKnownContentConsumers());
705            knowContentConsumers.add("duplicate-artifacts");
706            configuration.getRepositoryScanning().setKnownContentConsumers(knowContentConsumers);
707        }
708
709        Registry defaultOnlyConfiguration = readDefaultOnlyConfiguration();
710        // Currently we check only for configuration version change, not certain version numbers.
711        if (hasConfigVersionChanged(configuration, defaultOnlyConfiguration)) {
712            updateCheckPathDefaults(configuration, defaultOnlyConfiguration);
713            String newVersion = defaultOnlyConfiguration.getString("version");
714            if (newVersion == null) {
715                throw new IndeterminateConfigurationException("The default configuration has no version information!");
716            }
717            configuration.setVersion(newVersion);
718            try {
719                save(configuration);
720            } catch (IndeterminateConfigurationException e) {
721                log.error("Error occured during configuration update to new version: {}", e.getMessage());
722            } catch (RegistryException e) {
723                log.error("Error occured during configuration update to new version: {}", e.getMessage());
724            }
725        }
726    }
727
728    @Override
729    public void reload() {
730        this.configuration = null;
731        try {
732            this.registry.initialize();
733        } catch (RegistryException e) {
734            throw new ConfigurationRuntimeException(e.getMessage(), e);
735        }
736        this.initialize();
737    }
738
739    @Override
740    public Locale getDefaultLocale() {
741        return defaultLocale;
742    }
743
744    @Override
745    public List<Locale.LanguageRange> getLanguagePriorities() {
746        return languagePriorities;
747    }
748
749    @Override
750    public Path getAppServerBaseDir() {
751        String basePath = registry.getString("appserver.base");
752        if (!StringUtils.isEmpty(basePath)) {
753            return Paths.get(basePath);
754        } else {
755            return Paths.get("");
756        }
757    }
758
759    @Override
760    public Path getRepositoryBaseDir() {
761        if (repositoryBaseDirectory == null) {
762            getConfiguration();
763        }
764        return repositoryBaseDirectory;
765
766    }
767
768    @Override
769    public Path getRemoteRepositoryBaseDir() {
770        if (remoteRepositoryBaseDirectory == null) {
771            getConfiguration();
772        }
773        return remoteRepositoryBaseDirectory;
774    }
775
776    @Override
777    public Path getRepositoryGroupBaseDir() {
778        if (repositoryGroupBaseDirectory == null) {
779            getConfiguration();
780        }
781        return repositoryGroupBaseDirectory;
782    }
783
784    @Override
785    public Path getDataDirectory() {
786        if (dataDirectory == null) {
787            getConfiguration();
788        }
789        return dataDirectory;
790    }
791
792    @Override
793    public void beforeConfigurationChange(Registry registry, String propertyName, Object propertyValue) {
794        // nothing to do here
795    }
796
797    @Override
798    public synchronized void afterConfigurationChange(Registry registry, String propertyName, Object propertyValue) {
799        configuration = null;
800        this.dataDirectory = null;
801        this.repositoryBaseDirectory = null;
802    }
803
804    private String removeExpressions(String directory) {
805        String value = StringUtils.replace(directory, "${appserver.base}",
806                registry.getString("appserver.base", "${appserver.base}"));
807        value = StringUtils.replace(value, "${appserver.home}",
808                registry.getString("appserver.home", "${appserver.home}"));
809        return value;
810    }
811
812    private String unescapeCronExpression(String cronExpression) {
813        return StringUtils.replace(cronExpression, "\\,", ",");
814    }
815
816    private String escapeCronExpression(String cronExpression) {
817        return StringUtils.replace(cronExpression, ",", "\\,");
818    }
819
820    private Configuration unescapeExpressions(Configuration config) {
821        // TODO: for commons-configuration 1.3 only
822        for (ManagedRepositoryConfiguration c : config.getManagedRepositories()) {
823            c.setLocation(removeExpressions(c.getLocation()));
824            c.setRefreshCronExpression(unescapeCronExpression(c.getRefreshCronExpression()));
825        }
826
827        return config;
828    }
829
830    private Configuration checkRepositoryLocations(Configuration config) {
831        // additional check for [MRM-789], ensure that the location of the default repositories 
832        // are not installed in the server installation        
833        for (ManagedRepositoryConfiguration repo : (List<ManagedRepositoryConfiguration>) config.getManagedRepositories()) {
834            String repoPath = repo.getLocation();
835            Path repoLocation = Paths.get(repoPath);
836
837            if (Files.exists(repoLocation) && Files.isDirectory(repoLocation) && !repoPath.endsWith(
838                    "data/repositories/" + repo.getId())) {
839                repo.setLocation(repoPath + "/data/repositories/" + repo.getId());
840            }
841        }
842
843        return config;
844    }
845
846    public String getUserConfigFilename() {
847        return userConfigFilename;
848    }
849
850    public String getAltConfigFilename() {
851        return altConfigFilename;
852    }
853
854    @Override
855    public boolean isDefaulted() {
856        return this.isConfigurationDefaulted;
857    }
858
859    public Registry getRegistry() {
860        return registry;
861    }
862
863    public void setRegistry(Registry registry) {
864        this.registry = registry;
865    }
866
867
868    public void setUserConfigFilename(String userConfigFilename) {
869        this.userConfigFilename = userConfigFilename;
870    }
871
872    public void setAltConfigFilename(String altConfigFilename) {
873        this.altConfigFilename = altConfigFilename;
874    }
875}