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