1 package org.apache.maven.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.commons.collections.CollectionUtils;
23 import org.apache.commons.collections.MapUtils;
24 import org.apache.commons.io.FileUtils;
25 import org.apache.commons.lang.StringUtils;
26 import org.apache.maven.archiva.configuration.functors.ProxyConnectorConfigurationOrderComparator;
27 import org.apache.maven.archiva.configuration.io.registry.ConfigurationRegistryReader;
28 import org.apache.maven.archiva.configuration.io.registry.ConfigurationRegistryWriter;
29 import org.apache.maven.archiva.policies.AbstractUpdatePolicy;
30 import org.apache.maven.archiva.policies.CachedFailuresPolicy;
31 import org.apache.maven.archiva.policies.ChecksumPolicy;
32 import org.apache.maven.archiva.policies.DownloadPolicy;
33 import org.apache.maven.archiva.policies.PostDownloadPolicy;
34 import org.apache.maven.archiva.policies.PreDownloadPolicy;
35 import org.codehaus.plexus.evaluator.DefaultExpressionEvaluator;
36 import org.codehaus.plexus.evaluator.EvaluatorException;
37 import org.codehaus.plexus.evaluator.ExpressionEvaluator;
38 import org.codehaus.plexus.evaluator.sources.SystemPropertyExpressionSource;
39 import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable;
40 import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException;
41 import org.codehaus.plexus.registry.Registry;
42 import org.codehaus.plexus.registry.RegistryException;
43 import org.codehaus.plexus.registry.RegistryListener;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
48 import java.io.IOException;
49 import java.util.ArrayList;
50 import java.util.Collection;
51 import java.util.Collections;
52 import java.util.HashMap;
53 import java.util.HashSet;
54 import java.util.Iterator;
55 import java.util.List;
58 import java.util.Map.Entry;
62 * Implementation of configuration holder that retrieves it from the registry.
65 * The registry layers and merges the 2 configuration files: user, and application server.
68 * Instead of relying on the model defaults, if the registry is empty a default configuration file is loaded and
69 * applied from a resource. The defaults are not loaded into the registry as the lists (eg repositories) could no longer
70 * be removed if that was the case.
73 * When saving the configuration, it is saved to the location it was read from. If it was read from the defaults, it
74 * will be saved to the user location.
75 * However, if the configuration contains information from both sources, an exception is raised as this is currently
76 * unsupported. The reason for this is that it is not possible to identify where to re-save elements, and can result
77 * in list configurations (eg repositories) becoming inconsistent.
80 * If the configuration is outdated, it will be upgraded when it is loaded. This is done by checking the version flag
81 * before reading it from the registry.
84 * @plexus.component role="org.apache.maven.archiva.configuration.ArchivaConfiguration"
86 public class DefaultArchivaConfiguration
87 implements ArchivaConfiguration, RegistryListener, Initializable
89 private Logger log = LoggerFactory.getLogger(DefaultArchivaConfiguration.class);
92 * Plexus registry to read the configuration from.
94 * @plexus.requirement role-hint="commons-configuration"
96 private Registry registry;
99 * The configuration that has been converted.
101 private Configuration configuration;
104 * @plexus.requirement role="org.apache.maven.archiva.policies.PreDownloadPolicy"
106 private Map<String, PreDownloadPolicy> prePolicies;
109 * @plexus.requirement role="org.apache.maven.archiva.policies.PostDownloadPolicy"
111 private Map<String, PostDownloadPolicy> postPolicies;
114 * @plexus.configuration default-value="${user.home}/.m2/archiva.xml"
116 private String userConfigFilename;
119 * @plexus.configuration default-value="${appserver.base}/conf/archiva.xml"
121 private String altConfigFilename;
124 * Configuration Listeners we've registered.
126 private Set<ConfigurationListener> listeners = new HashSet<ConfigurationListener>();
129 * Registry Listeners we've registered.
131 private Set<RegistryListener> registryListeners = new HashSet<RegistryListener>();
134 * Boolean to help determine if the configuration exists as a result of pulling in
135 * the default-archiva.xml
137 private boolean isConfigurationDefaulted = false;
139 private static final String KEY = "org.apache.maven.archiva";
141 public synchronized Configuration getConfiguration()
143 if ( configuration == null )
145 configuration = load();
146 configuration = processExpressions( configuration );
149 return configuration;
152 private Configuration load()
154 // TODO: should this be the same as section? make sure unnamed sections still work (eg, sys properties)
155 Registry subset = registry.getSubset( KEY );
156 if ( subset.getString( "version" ) == null )
158 // a little autodetection of v1, even if version is omitted (this was previously allowed)
159 if ( subset.getSubset( "repositoryScanning" ).isEmpty() )
161 // only for empty, or v < 1
162 subset = readDefaultConfiguration();
166 Configuration config = new ConfigurationRegistryReader().read( subset );
168 if ( !config.getRepositories().isEmpty() )
170 for ( Iterator<V1RepositoryConfiguration> i = config.getRepositories().iterator(); i.hasNext(); )
172 V1RepositoryConfiguration r = i.next();
173 r.setScanned( r.isIndexed() );
175 if ( r.getUrl().startsWith( "file://" ) )
177 r.setLocation( r.getUrl().substring( 7 ) );
178 config.addManagedRepository( r );
180 else if ( r.getUrl().startsWith( "file:" ) )
182 r.setLocation( r.getUrl().substring( 5 ) );
183 config.addManagedRepository( r );
187 RemoteRepositoryConfiguration repo = new RemoteRepositoryConfiguration();
188 repo.setId( r.getId() );
189 repo.setLayout( r.getLayout() );
190 repo.setName( r.getName() );
191 repo.setUrl( r.getUrl() );
192 config.addRemoteRepository( repo );
196 // Prevent duplicate repositories from showing up.
197 config.getRepositories().clear();
199 registry.removeSubset( KEY + ".repositories" );
202 if ( !CollectionUtils.isEmpty( config.getRemoteRepositories() ) )
204 List<RemoteRepositoryConfiguration> remoteRepos = config.getRemoteRepositories();
205 for ( RemoteRepositoryConfiguration repo : remoteRepos )
207 // [MRM-582] Remote Repositories with empty <username> and <password> fields shouldn't be created in configuration.
208 if( StringUtils.isBlank( repo.getUsername() ) )
210 repo.setUsername( null );
213 if( StringUtils.isBlank( repo.getPassword() ) )
215 repo.setPassword( null );
220 if ( !config.getProxyConnectors().isEmpty() )
222 // Fix Proxy Connector Settings.
224 List<ProxyConnectorConfiguration> proxyConnectorList = new ArrayList<ProxyConnectorConfiguration>();
225 // Create a copy of the list to read from (to prevent concurrent modification exceptions)
226 proxyConnectorList.addAll( config.getProxyConnectors() );
227 // Remove the old connector list.
228 config.getProxyConnectors().clear();
230 for ( ProxyConnectorConfiguration connector : proxyConnectorList )
233 boolean connectorValid = true;
235 Map<String, String> policies = new HashMap<String, String>();
236 // Make copy of policies
237 policies.putAll( connector.getPolicies() );
238 // Clear out policies
239 connector.getPolicies().clear();
241 // Work thru policies. cleaning them up.
242 for ( Entry<String, String> entry : policies.entrySet() )
244 String policyId = entry.getKey();
245 String setting = entry.getValue();
247 // Upgrade old policy settings.
248 if ( "releases".equals( policyId ) || "snapshots".equals( policyId ) )
250 if ( "ignored".equals( setting ) )
252 setting = AbstractUpdatePolicy.ALWAYS;
254 else if ( "disabled".equals( setting ) )
256 setting = AbstractUpdatePolicy.NEVER;
259 else if ( "cache-failures".equals( policyId ) )
261 if ( "ignored".equals( setting ) )
263 setting = CachedFailuresPolicy.NO;
265 else if ( "cached".equals( setting ) )
267 setting = CachedFailuresPolicy.YES;
270 else if ( "checksum".equals( policyId ) )
272 if ( "ignored".equals( setting ) )
274 setting = ChecksumPolicy.IGNORE;
278 // Validate existance of policy key.
279 if ( policyExists( policyId ) )
281 DownloadPolicy policy = findPolicy( policyId );
282 // Does option exist?
283 if ( !policy.getOptions().contains( setting ) )
285 setting = policy.getDefaultOption();
287 connector.addPolicy( policyId, setting );
291 // Policy key doesn't exist. Don't add it to golden version.
292 log.warn( "Policy [" + policyId + "] does not exist." );
296 if ( connectorValid )
298 config.addProxyConnector( connector );
302 // Normalize the order fields in the proxy connectors.
303 Map<String, java.util.List<ProxyConnectorConfiguration>> proxyConnectorMap = config
304 .getProxyConnectorAsMap();
306 for ( String key : proxyConnectorMap.keySet() )
308 List<ProxyConnectorConfiguration> connectors = proxyConnectorMap.get( key );
309 // Sort connectors by order field.
310 Collections.sort( connectors, ProxyConnectorConfigurationOrderComparator.getInstance() );
312 // Normalize the order field values.
314 for ( ProxyConnectorConfiguration connector : connectors )
316 connector.setOrder( order++ );
324 private DownloadPolicy findPolicy( String policyId )
326 if ( MapUtils.isEmpty( prePolicies ) )
328 log.error( "No PreDownloadPolicies found!" );
332 if ( MapUtils.isEmpty( postPolicies ) )
334 log.error( "No PostDownloadPolicies found!" );
338 DownloadPolicy policy;
340 policy = prePolicies.get( policyId );
341 if ( policy != null )
346 policy = postPolicies.get( policyId );
347 if ( policy != null )
355 private boolean policyExists( String policyId )
357 if ( MapUtils.isEmpty( prePolicies ) )
359 log.error( "No PreDownloadPolicies found!" );
363 if ( MapUtils.isEmpty( postPolicies ) )
365 log.error( "No PostDownloadPolicies found!" );
369 return ( prePolicies.containsKey( policyId ) || postPolicies.containsKey( policyId ) );
372 private Registry readDefaultConfiguration()
374 // if it contains some old configuration, remove it (Archiva 0.9)
375 registry.removeSubset( KEY );
379 registry.addConfigurationFromResource( "org/apache/maven/archiva/configuration/default-archiva.xml", KEY );
380 this.isConfigurationDefaulted = true;
382 catch ( RegistryException e )
384 throw new ConfigurationRuntimeException(
385 "Fatal error: Unable to find the built-in default configuration and load it into the registry",
388 return registry.getSubset( KEY );
391 public synchronized void save( Configuration configuration )
392 throws RegistryException, IndeterminateConfigurationException
394 Registry section = registry.getSection( KEY + ".user" );
395 Registry baseSection = registry.getSection( KEY + ".base" );
396 if ( section == null )
398 section = baseSection;
399 if ( section == null )
401 section = createDefaultConfigurationFile();
404 else if ( baseSection != null )
406 Collection<String> keys = baseSection.getKeys();
407 boolean foundList = false;
408 for ( Iterator<String> i = keys.iterator(); i.hasNext() && !foundList; )
410 String key = i.next();
412 // a little aggressive with the repositoryScanning and databaseScanning - should be no need to split
413 // that configuration
414 if ( key.startsWith( "repositories" ) || key.startsWith( "proxyConnectors" )
415 || key.startsWith( "networkProxies" ) || key.startsWith( "repositoryScanning" )
416 || key.startsWith( "databaseScanning" ) || key.startsWith( "remoteRepositories" )
417 || key.startsWith( "managedRepositories" ) )
425 this.configuration = null;
427 throw new IndeterminateConfigurationException(
428 "Configuration can not be saved when it is loaded from two sources" );
432 // escape all cron expressions to handle ','
433 for ( Iterator<ManagedRepositoryConfiguration> i = configuration.getManagedRepositories().iterator(); i
436 ManagedRepositoryConfiguration c = i.next();
437 c.setRefreshCronExpression( escapeCronExpression( c.getRefreshCronExpression() ) );
440 if ( configuration.getDatabaseScanning() != null )
442 configuration.getDatabaseScanning().setCronExpression(
443 escapeCronExpression( configuration
444 .getDatabaseScanning().getCronExpression() ) );
447 new ConfigurationRegistryWriter().write( configuration, section );
450 triggerEvent( ConfigurationEvent.SAVED );
452 this.configuration = processExpressions( configuration );
455 private Registry createDefaultConfigurationFile()
456 throws RegistryException
458 // TODO: may not be needed under commons-configuration 1.4 - check
459 // UPDATE: Upgrading to commons-configuration 1.4 breaks half the unit tests. 2007-10-11 (joakime)
461 String contents = "<configuration />";
462 if ( !writeFile( "user configuration", userConfigFilename, contents ) )
464 if ( !writeFile( "alternative configuration", altConfigFilename, contents ) )
466 throw new RegistryException( "Unable to create configuration file in either user ["
467 + userConfigFilename + "] or alternative [" + altConfigFilename
468 + "] locations on disk, usually happens when not allowed to write to those locations." );
474 ( (Initializable) registry ).initialize();
476 for ( RegistryListener regListener : registryListeners )
478 addRegistryChangeListener( regListener );
481 catch ( InitializationException e )
483 throw new RegistryException( "Unable to reinitialize configuration: " + e.getMessage(), e );
486 triggerEvent( ConfigurationEvent.SAVED );
488 return registry.getSection( KEY + ".user" );
492 * Attempts to write the contents to a file, if an IOException occurs, return false.
494 * @param filetype the filetype (freeform text) to use in logging messages when failure to write.
495 * @param path the path to write to.
496 * @param contents the contents to write.
497 * @return true if write successful.
499 private boolean writeFile( String filetype, String path, String contents )
501 File file = new File( path );
505 FileUtils.writeStringToFile( file, contents, "UTF-8" );
508 catch ( IOException e )
510 log.error( "Unable to create " + filetype + " file: " + e.getMessage(), e );
515 private void triggerEvent( int type )
517 ConfigurationEvent evt = new ConfigurationEvent( type );
518 for ( ConfigurationListener listener : listeners )
522 listener.configurationEvent( evt );
524 catch ( Throwable t )
526 log.warn( "Unable to notify of saved configuration event.", t );
531 public void addListener( ConfigurationListener listener )
533 if ( listener == null )
538 listeners.add( listener );
541 public void removeListener( ConfigurationListener listener )
543 if ( listener == null )
548 listeners.remove( listener );
551 public void addChangeListener( RegistryListener listener )
553 addRegistryChangeListener( listener );
555 // keep track for later
556 registryListeners.add( listener );
559 private void addRegistryChangeListener( RegistryListener listener )
561 Registry section = registry.getSection( KEY + ".user" );
562 if ( section != null )
564 section.addChangeListener( listener );
566 section = registry.getSection( KEY + ".base" );
567 if ( section != null )
569 section.addChangeListener( listener );
573 public void initialize()
574 throws InitializationException
576 // Resolve expressions in the userConfigFilename and altConfigFilename
579 ExpressionEvaluator expressionEvaluator = new DefaultExpressionEvaluator();
580 expressionEvaluator.addExpressionSource( new SystemPropertyExpressionSource() );
581 userConfigFilename = expressionEvaluator.expand( userConfigFilename );
582 altConfigFilename = expressionEvaluator.expand( altConfigFilename );
584 catch ( EvaluatorException e )
586 throw new InitializationException( "Unable to evaluate expressions found in "
587 + "userConfigFilename or altConfigFilename." );
590 registry.addChangeListener( this );
593 public void beforeConfigurationChange( Registry registry, String propertyName, Object propertyValue )
595 // nothing to do here
598 public synchronized void afterConfigurationChange( Registry registry, String propertyName, Object propertyValue )
600 configuration = null;
603 private String removeExpressions( String directory )
605 String value = StringUtils.replace( directory, "${appserver.base}", registry.getString( "appserver.base",
606 "${appserver.base}" ) );
607 value = StringUtils.replace( value, "${appserver.home}", registry.getString( "appserver.home",
608 "${appserver.home}" ) );
612 private String unescapeCronExpression( String cronExpression )
614 return StringUtils.replace( cronExpression, "\\,", "," );
617 private String escapeCronExpression( String cronExpression )
619 return StringUtils.replace( cronExpression, ",", "\\," );
622 private Configuration processExpressions( Configuration config )
624 // TODO: for commons-configuration 1.3 only
625 for ( Iterator<ManagedRepositoryConfiguration> i = config.getManagedRepositories().iterator(); i.hasNext(); )
627 ManagedRepositoryConfiguration c = i.next();
628 c.setLocation( removeExpressions( c.getLocation() ) );
629 c.setRefreshCronExpression( unescapeCronExpression( c.getRefreshCronExpression() ) );
632 DatabaseScanningConfiguration databaseScanning = config.getDatabaseScanning();
633 if ( databaseScanning != null )
635 String cron = databaseScanning.getCronExpression();
636 databaseScanning.setCronExpression( unescapeCronExpression( cron ) );
642 public String getUserConfigFilename()
644 return userConfigFilename;
647 public String getAltConfigFilename()
649 return altConfigFilename;
652 public boolean isDefaulted()
654 return this.isConfigurationDefaulted;