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