1 package org.apache.archiva.configuration;
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
12 * http://www.apache.org/licenses/LICENSE-2.0
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
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.Policy;
29 import org.apache.archiva.policies.PostDownloadPolicy;
30 import org.apache.archiva.policies.PreDownloadPolicy;
31 import org.apache.commons.collections.CollectionUtils;
32 import org.apache.commons.collections.MapUtils;
33 import org.apache.commons.configuration.BaseConfiguration;
34 import org.apache.commons.io.FileUtils;
35 import org.apache.commons.lang.StringUtils;
36 import org.codehaus.plexus.evaluator.DefaultExpressionEvaluator;
37 import org.codehaus.plexus.evaluator.EvaluatorException;
38 import org.codehaus.plexus.evaluator.ExpressionEvaluator;
39 import org.codehaus.plexus.evaluator.sources.SystemPropertyExpressionSource;
40 import org.codehaus.plexus.registry.Registry;
41 import org.codehaus.plexus.registry.RegistryException;
42 import org.codehaus.plexus.registry.RegistryListener;
43 import org.codehaus.redback.components.registry.commons.CommonsConfigurationRegistry;
44 import org.codehaus.redback.components.springutils.ComponentContainer;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47 import org.springframework.stereotype.Service;
49 import javax.annotation.PostConstruct;
50 import javax.inject.Inject;
51 import javax.inject.Named;
53 import java.io.IOException;
54 import java.util.ArrayList;
55 import java.util.Arrays;
56 import java.util.Collection;
57 import java.util.Collections;
58 import java.util.HashMap;
59 import java.util.HashSet;
60 import java.util.Iterator;
61 import java.util.List;
63 import java.util.Map.Entry;
68 * Implementation of configuration holder that retrieves it from the registry.
71 * The registry layers and merges the 2 configuration files: user, and application server.
74 * Instead of relying on the model defaults, if the registry is empty a default configuration file is loaded and
75 * applied from a resource. The defaults are not loaded into the registry as the lists (eg repositories) could no longer
76 * be removed if that was the case.
79 * When saving the configuration, it is saved to the location it was read from. If it was read from the defaults, it
80 * will be saved to the user location.
81 * However, if the configuration contains information from both sources, an exception is raised as this is currently
82 * unsupported. The reason for this is that it is not possible to identify where to re-save elements, and can result
83 * in list configurations (eg repositories) becoming inconsistent.
86 * If the configuration is outdated, it will be upgraded when it is loaded. This is done by checking the version flag
87 * before reading it from the registry.
90 @Service( "archivaConfiguration#default" )
91 public class DefaultArchivaConfiguration
92 implements ArchivaConfiguration, RegistryListener
94 private Logger log = LoggerFactory.getLogger( DefaultArchivaConfiguration.class );
97 * Plexus registry to read the configuration from.
100 @Named( value = "commons-configuration" )
101 private Registry registry;
104 private ComponentContainer componentContainer;
107 * The configuration that has been converted.
109 private Configuration configuration;
114 * @todo these don't strictly belong in here
116 private Map<String, PreDownloadPolicy> prePolicies;
121 * @todo these don't strictly belong in here
123 private Map<String, PostDownloadPolicy> postPolicies;
127 * default-value="${user.home}/.m2/archiva.xml"
129 private String userConfigFilename = "${user.home}/.m2/archiva.xml";
133 * default-value="${appserver.base}/conf/archiva.xml"
135 private String altConfigFilename = "${appserver.base}/conf/archiva.xml";
138 * Configuration Listeners we've registered.
140 private Set<ConfigurationListener> listeners = new HashSet<ConfigurationListener>( );
143 * Registry Listeners we've registered.
145 private Set<RegistryListener> registryListeners = new HashSet<RegistryListener>( );
148 * Boolean to help determine if the configuration exists as a result of pulling in
149 * the default-archiva.xml
151 private boolean isConfigurationDefaulted = false;
153 private static final String KEY = "org.apache.archiva";
155 public Configuration getConfiguration( )
157 return loadConfiguration( );
160 private synchronized Configuration loadConfiguration( )
162 if ( configuration == null )
164 configuration = load( );
165 configuration = unescapeExpressions( configuration );
166 if ( isConfigurationDefaulted )
168 configuration = checkRepositoryLocations( configuration );
172 return configuration;
175 @SuppressWarnings( "unchecked" )
176 private Configuration load( )
178 // TODO: should this be the same as section? make sure unnamed sections still work (eg, sys properties)
179 Registry subset = registry.getSubset( KEY );
180 if ( subset.getString( "version" ) == null )
182 // a little autodetection of v1, even if version is omitted (this was previously allowed)
183 if ( subset.getSubset( "repositoryScanning" ).isEmpty( ) )
185 // only for empty, or v < 1
186 subset = readDefaultConfiguration( );
190 Configuration config = new ConfigurationRegistryReader( ).read( subset );
192 config.getRepositoryGroups( );
193 config.getRepositoryGroupsAsMap( );
194 if ( !config.getRepositories( ).isEmpty( ) )
196 for ( Iterator<V1RepositoryConfiguration> i = config.getRepositories( ).iterator( ); i.hasNext( ); )
198 V1RepositoryConfiguration r = i.next( );
199 r.setScanned( r.isIndexed( ) );
201 if ( StringUtils.startsWith( r.getUrl( ), "file://" ) )
203 r.setLocation( r.getUrl( ).substring( 7 ) );
204 config.addManagedRepository( r );
206 else if ( StringUtils.startsWith( r.getUrl( ), "file:" ) )
208 r.setLocation( r.getUrl( ).substring( 5 ) );
209 config.addManagedRepository( r );
211 else if ( StringUtils.isEmpty( r.getUrl( ) ) )
213 // in case of empty url we can consider it as a managed one
214 // check if location is null
215 //file://${appserver.base}/repositories/${id}
216 if ( StringUtils.isEmpty( r.getLocation( ) ) )
218 r.setLocation( "file://${appserver.base}/repositories/" + r.getId( ) );
220 config.addManagedRepository( r );
224 RemoteRepositoryConfiguration repo = new RemoteRepositoryConfiguration( );
225 repo.setId( r.getId( ) );
226 repo.setLayout( r.getLayout( ) );
227 repo.setName( r.getName( ) );
228 repo.setUrl( r.getUrl( ) );
229 config.addRemoteRepository( repo );
233 // Prevent duplicate repositories from showing up.
234 config.getRepositories( ).clear( );
236 registry.removeSubset( KEY + ".repositories" );
239 if ( !CollectionUtils.isEmpty( config.getRemoteRepositories( ) ) )
241 List<RemoteRepositoryConfiguration> remoteRepos = config.getRemoteRepositories( );
242 for ( RemoteRepositoryConfiguration repo : remoteRepos )
244 // [MRM-582] Remote Repositories with empty <username> and <password> fields shouldn't be created in configuration.
245 if ( StringUtils.isBlank( repo.getUsername( ) ) )
247 repo.setUsername( null );
250 if ( StringUtils.isBlank( repo.getPassword( ) ) )
252 repo.setPassword( null );
257 if ( !config.getProxyConnectors( ).isEmpty( ) )
259 // Fix Proxy Connector Settings.
261 List<ProxyConnectorConfiguration> proxyConnectorList = new ArrayList<ProxyConnectorConfiguration>( );
262 // Create a copy of the list to read from (to prevent concurrent modification exceptions)
263 proxyConnectorList.addAll( config.getProxyConnectors( ) );
264 // Remove the old connector list.
265 config.getProxyConnectors( ).clear( );
267 for ( ProxyConnectorConfiguration connector : proxyConnectorList )
270 boolean connectorValid = true;
272 Map<String, String> policies = new HashMap<String, String>( );
273 // Make copy of policies
274 policies.putAll( connector.getPolicies( ) );
275 // Clear out policies
276 connector.getPolicies( ).clear( );
278 // Work thru policies. cleaning them up.
279 for ( Entry<String, String> entry : policies.entrySet( ) )
281 String policyId = entry.getKey( );
282 String setting = entry.getValue( );
284 // Upgrade old policy settings.
285 if ( "releases".equals( policyId ) || "snapshots".equals( policyId ) )
287 if ( "ignored".equals( setting ) )
289 setting = AbstractUpdatePolicy.ALWAYS;
291 else if ( "disabled".equals( setting ) )
293 setting = AbstractUpdatePolicy.NEVER;
296 else if ( "cache-failures".equals( policyId ) )
298 if ( "ignored".equals( setting ) )
300 setting = CachedFailuresPolicy.NO;
302 else if ( "cached".equals( setting ) )
304 setting = CachedFailuresPolicy.YES;
307 else if ( "checksum".equals( policyId ) )
309 if ( "ignored".equals( setting ) )
311 setting = ChecksumPolicy.IGNORE;
315 // Validate existance of policy key.
316 if ( policyExists( policyId ) )
318 Policy policy = findPolicy( policyId );
319 // Does option exist?
320 if ( !policy.getOptions( ).contains( setting ) )
322 setting = policy.getDefaultOption( );
324 connector.addPolicy( policyId, setting );
328 // Policy key doesn't exist. Don't add it to golden version.
329 log.warn( "Policy [" + policyId + "] does not exist." );
333 if ( connectorValid )
335 config.addProxyConnector( connector );
339 // Normalize the order fields in the proxy connectors.
340 Map<String, java.util.List<ProxyConnectorConfiguration>> proxyConnectorMap =
341 config.getProxyConnectorAsMap( );
343 for ( List<ProxyConnectorConfiguration> connectors : proxyConnectorMap.values( ) )
345 // Sort connectors by order field.
346 Collections.sort( connectors, ProxyConnectorConfigurationOrderComparator.getInstance( ) );
348 // Normalize the order field values.
350 for ( ProxyConnectorConfiguration connector : connectors )
352 connector.setOrder( order++ );
360 private Policy findPolicy( String policyId )
362 if ( MapUtils.isEmpty( prePolicies ) )
364 log.error( "No PreDownloadPolicies found!" );
368 if ( MapUtils.isEmpty( postPolicies ) )
370 log.error( "No PostDownloadPolicies found!" );
376 policy = prePolicies.get( policyId );
377 if ( policy != null )
382 policy = postPolicies.get( policyId );
383 if ( policy != null )
391 private boolean policyExists( String policyId )
393 if ( MapUtils.isEmpty( prePolicies ) )
395 log.error( "No PreDownloadPolicies found!" );
399 if ( MapUtils.isEmpty( postPolicies ) )
401 log.error( "No PostDownloadPolicies found!" );
405 return ( prePolicies.containsKey( policyId ) || postPolicies.containsKey( policyId ) );
408 private Registry readDefaultConfiguration( )
410 // if it contains some old configuration, remove it (Archiva 0.9)
411 registry.removeSubset( KEY );
415 registry.addConfigurationFromResource( "org/apache/archiva/configuration/default-archiva.xml", KEY );
416 this.isConfigurationDefaulted = true;
418 catch ( RegistryException e )
420 throw new ConfigurationRuntimeException(
421 "Fatal error: Unable to find the built-in default configuration and load it into the registry", e );
423 return registry.getSubset( KEY );
426 @SuppressWarnings( "unchecked" )
427 public synchronized void save( Configuration configuration )
428 throws IndeterminateConfigurationException, RegistryException
430 Registry section = registry.getSection( KEY + ".user" );
431 Registry baseSection = registry.getSection( KEY + ".base" );
432 if ( section == null )
434 section = baseSection;
435 if ( section == null )
437 section = createDefaultConfigurationFile( );
440 else if ( baseSection != null )
442 Collection<String> keys = baseSection.getKeys( );
443 boolean foundList = false;
444 for ( Iterator<String> i = keys.iterator( ); i.hasNext( ) && !foundList; )
446 String key = i.next( );
448 // a little aggressive with the repositoryScanning and databaseScanning - should be no need to split
449 // that configuration
450 if ( key.startsWith( "repositories" ) || key.startsWith( "proxyConnectors" ) || key.startsWith(
451 "networkProxies" ) || key.startsWith( "repositoryScanning" ) || key.startsWith(
452 "remoteRepositories" ) || key.startsWith( "managedRepositories" ) || key.startsWith(
453 "repositoryGroups" ) )
461 this.configuration = null;
463 throw new IndeterminateConfigurationException(
464 "Configuration can not be saved when it is loaded from two sources" );
468 // escape all cron expressions to handle ','
469 escapeCronExpressions( configuration );
471 // [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.
472 if ( configuration.getManagedRepositories( ).isEmpty( ) && section != null )
474 section.removeSubset( "managedRepositories" );
476 if ( configuration.getRemoteRepositories( ).isEmpty( ) && section != null )
478 section.removeSubset( "remoteRepositories" );
481 if ( configuration.getProxyConnectors( ).isEmpty( ) && section != null )
483 section.removeSubset( "proxyConnectors" );
485 if ( configuration.getNetworkProxies( ).isEmpty( ) && section != null )
487 section.removeSubset( "networkProxies" );
489 if ( configuration.getLegacyArtifactPaths( ).isEmpty( ) && section != null )
491 section.removeSubset( "legacyArtifactPaths" );
493 if ( configuration.getRepositoryGroups( ).isEmpty( ) && section != null )
495 section.removeSubset( "repositoryGroups" );
497 if ( configuration.getRepositoryScanning( ) != null )
499 if ( configuration.getRepositoryScanning( ).getKnownContentConsumers( ).isEmpty( ) && section != null )
501 section.removeSubset( "repositoryScanning.knownContentConsumers" );
503 if ( configuration.getRepositoryScanning( ).getInvalidContentConsumers( ).isEmpty( ) && section != null )
505 section.removeSubset( "repositoryScanning.invalidContentConsumers" );
509 new ConfigurationRegistryWriter( ).write( configuration, section );
512 this.configuration = unescapeExpressions( configuration );
514 triggerEvent( ConfigurationEvent.SAVED );
517 private void escapeCronExpressions( Configuration configuration )
519 for ( ManagedRepositoryConfiguration c : (List<ManagedRepositoryConfiguration>) configuration.getManagedRepositories( ) )
521 c.setRefreshCronExpression( escapeCronExpression( c.getRefreshCronExpression( ) ) );
527 private Registry createDefaultConfigurationFile( )
528 throws RegistryException
530 // TODO: may not be needed under commons-configuration 1.4 - check
531 // UPDATE: Upgrading to commons-configuration 1.4 breaks half the unit tests. 2007-10-11 (joakime)
533 String contents = "<configuration />";
535 String fileLocation = userConfigFilename;
537 if ( !writeFile( "user configuration", userConfigFilename, contents ) )
539 fileLocation = altConfigFilename;
540 if ( !writeFile( "alternative configuration", altConfigFilename, contents ) )
542 throw new RegistryException(
543 "Unable to create configuration file in either user [" + userConfigFilename + "] or alternative ["
545 + "] locations on disk, usually happens when not allowed to write to those locations." );
549 // olamy hackish I know :-)
550 contents = "<configuration><xml fileName=\"" + fileLocation
551 + "\" config-forceCreate=\"true\" config-name=\"org.apache.archiva.user\"/>" + "</configuration>";
553 ( (CommonsConfigurationRegistry) registry ).setProperties( contents );
555 registry.initialize( );
557 for ( RegistryListener regListener : registryListeners )
559 addRegistryChangeListener( regListener );
562 triggerEvent( ConfigurationEvent.SAVED );
564 Registry section = registry.getSection( KEY + ".user" );
565 return section == null ? new CommonsConfigurationRegistry( new BaseConfiguration( ) ) : section;
569 * Attempts to write the contents to a file, if an IOException occurs, return false.
571 * The file will be created if the directory to the file exists, otherwise this will return false.
573 * @param filetype the filetype (freeform text) to use in logging messages when failure to write.
574 * @param path the path to write to.
575 * @param contents the contents to write.
576 * @return true if write successful.
578 private boolean writeFile( String filetype, String path, String contents )
580 File file = new File( path );
584 // Check parent directory (if it is declared)
585 if ( file.getParentFile( ) != null )
587 // Check that directory exists
588 if ( ( file.getParentFile( ).exists( ) == false ) || ( file.getParentFile( ).isDirectory( ) == false ) )
590 // Directory to file must exist for file to be created
595 FileUtils.writeStringToFile( file, contents, "UTF-8" );
598 catch ( IOException e )
600 log.error( "Unable to create " + filetype + " file: " + e.getMessage( ), e );
605 private void triggerEvent( int type )
607 ConfigurationEvent evt = new ConfigurationEvent( type );
608 for ( ConfigurationListener listener : listeners )
610 listener.configurationEvent( evt );
614 public void addListener( ConfigurationListener listener )
616 if ( listener == null )
621 listeners.add( listener );
624 public void removeListener( ConfigurationListener listener )
626 if ( listener == null )
631 listeners.remove( listener );
634 public void addChangeListener( RegistryListener listener )
636 addRegistryChangeListener( listener );
638 // keep track for later
639 registryListeners.add( listener );
642 private void addRegistryChangeListener( RegistryListener listener )
644 Registry section = registry.getSection( KEY + ".user" );
645 if ( section != null )
647 section.addChangeListener( listener );
649 section = registry.getSection( KEY + ".base" );
650 if ( section != null )
652 section.addChangeListener( listener );
657 public void initialize( )
660 this.postPolicies = componentContainer.buildMapWithRole( PostDownloadPolicy.class );
661 this.prePolicies = componentContainer.buildMapWithRole( PreDownloadPolicy.class );
662 // Resolve expressions in the userConfigFilename and altConfigFilename
665 ExpressionEvaluator expressionEvaluator = new DefaultExpressionEvaluator( );
666 expressionEvaluator.addExpressionSource( new SystemPropertyExpressionSource( ) );
667 String userConfigFileNameSysProps = System.getProperty( "archiva.user.configFileName" );
668 if ( StringUtils.isNotBlank( userConfigFileNameSysProps ) )
670 userConfigFilename = userConfigFileNameSysProps;
674 userConfigFilename = expressionEvaluator.expand( userConfigFilename );
676 altConfigFilename = expressionEvaluator.expand( altConfigFilename );
677 loadConfiguration( );
678 handleUpgradeConfiguration( );
680 catch ( IndeterminateConfigurationException e )
682 throw new RuntimeException( "failed during upgrade from previous version" + e.getMessage( ), e );
684 catch ( RegistryException e )
686 throw new RuntimeException( "failed during upgrade from previous version" + e.getMessage( ), e );
688 catch ( EvaluatorException e )
690 throw new RuntimeException(
691 "Unable to evaluate expressions found in " + "userConfigFilename or altConfigFilename." );
693 registry.addChangeListener( this );
699 private void handleUpgradeConfiguration( )
700 throws RegistryException, IndeterminateConfigurationException
703 // remove database consumers
704 List<String> dbConsumers = Arrays.asList( "update-db-artifact", "update-db-repository-metadata" );
705 List<String> knowContentConsumers = new ArrayList<String>( );
706 for ( String knowContentConsumer : configuration.getRepositoryScanning( ).getKnownContentConsumers( ) )
708 if ( !dbConsumers.contains( knowContentConsumer ) )
710 knowContentConsumers.add( knowContentConsumer );
714 configuration.getRepositoryScanning( ).setKnownContentConsumers( knowContentConsumers );
715 save( configuration );
717 // ensure create-archiva-metadata is here
718 if ( !configuration.getRepositoryScanning( ).getKnownContentConsumers( ).contains( "create-archiva-metadata" ) )
720 knowContentConsumers =
721 new ArrayList<String>( configuration.getRepositoryScanning( ).getKnownContentConsumers( ) );
722 knowContentConsumers.add( "create-archiva-metadata" );
723 configuration.getRepositoryScanning( ).setKnownContentConsumers( knowContentConsumers );
724 save( configuration );
727 // ensure duplicate-artifacts is here
728 if ( !configuration.getRepositoryScanning( ).getKnownContentConsumers( ).contains( "duplicate-artifacts" ) )
730 knowContentConsumers =
731 new ArrayList<String>( configuration.getRepositoryScanning( ).getKnownContentConsumers( ) );
732 knowContentConsumers.add( "duplicate-artifacts" );
733 configuration.getRepositoryScanning( ).setKnownContentConsumers( knowContentConsumers );
734 save( configuration );
738 public void reload( )
740 this.configuration = null;
743 this.registry.initialize( );
745 catch ( RegistryException e )
747 throw new ConfigurationRuntimeException( e.getMessage( ), e );
752 public void beforeConfigurationChange( Registry registry, String propertyName, Object propertyValue )
754 // nothing to do here
757 public synchronized void afterConfigurationChange( Registry registry, String propertyName, Object propertyValue )
759 configuration = null;
762 private String removeExpressions( String directory )
764 String value = StringUtils.replace( directory, "${appserver.base}",
765 registry.getString( "appserver.base", "${appserver.base}" ) );
766 value = StringUtils.replace( value, "${appserver.home}",
767 registry.getString( "appserver.home", "${appserver.home}" ) );
771 private String unescapeCronExpression( String cronExpression )
773 return StringUtils.replace( cronExpression, "\\,", "," );
776 private String escapeCronExpression( String cronExpression )
778 return StringUtils.replace( cronExpression, ",", "\\," );
781 private Configuration unescapeExpressions( Configuration config )
783 // TODO: for commons-configuration 1.3 only
784 for ( Iterator<ManagedRepositoryConfiguration> i = config.getManagedRepositories( ).iterator( ); i.hasNext( ); )
786 ManagedRepositoryConfiguration c = i.next( );
787 c.setLocation( removeExpressions( c.getLocation( ) ) );
788 c.setRefreshCronExpression( unescapeCronExpression( c.getRefreshCronExpression( ) ) );
794 private Configuration checkRepositoryLocations( Configuration config )
796 // additional check for [MRM-789], ensure that the location of the default repositories
797 // are not installed in the server installation
798 for ( ManagedRepositoryConfiguration repo : (List<ManagedRepositoryConfiguration>) config.getManagedRepositories( ) )
800 String repoPath = repo.getLocation( );
801 File repoLocation = new File( repoPath );
803 if ( repoLocation.exists( ) && repoLocation.isDirectory( ) && !repoPath.endsWith(
804 "data/repositories/" + repo.getId( ) ) )
806 repo.setLocation( repoPath + "/data/repositories/" + repo.getId( ) );
813 public String getUserConfigFilename( )
815 return userConfigFilename;
818 public String getAltConfigFilename( )
820 return altConfigFilename;
823 public boolean isDefaulted( )
825 return this.isConfigurationDefaulted;
828 public Registry getRegistry( )
833 public void setRegistry( Registry registry )
835 this.registry = registry;
839 public void setUserConfigFilename( String userConfigFilename )
841 this.userConfigFilename = userConfigFilename;
844 public void setAltConfigFilename( String altConfigFilename )
846 this.altConfigFilename = altConfigFilename;