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.logging.AbstractLogEnabled;
40 import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable;
41 import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException;
42 import org.codehaus.plexus.registry.Registry;
43 import org.codehaus.plexus.registry.RegistryException;
44 import org.codehaus.plexus.registry.RegistryListener;
47 import java.io.IOException;
48 import java.util.ArrayList;
49 import java.util.Collection;
50 import java.util.Collections;
51 import java.util.HashMap;
52 import java.util.HashSet;
53 import java.util.Iterator;
54 import java.util.List;
56 import java.util.Map.Entry;
61 * Implementation of configuration holder that retrieves it from the registry.
64 * The registry layers and merges the 2 configuration files: user, and application server.
67 * Instead of relying on the model defaults, if the registry is empty a default configuration file is loaded and
68 * applied from a resource. The defaults are not loaded into the registry as the lists (eg repositories) could no longer
69 * be removed if that was the case.
72 * When saving the configuration, it is saved to the location it was read from. If it was read from the defaults, it
73 * will be saved to the user location.
74 * However, if the configuration contains information from both sources, an exception is raised as this is currently
75 * unsupported. The reason for this is that it is not possible to identify where to re-save elements, and can result
76 * in list configurations (eg repositories) becoming inconsistent.
79 * If the configuration is outdated, it will be upgraded when it is loaded. This is done by checking the version flag
80 * before reading it from the registry.
83 * @plexus.component role="org.apache.maven.archiva.configuration.ArchivaConfiguration"
85 public class DefaultArchivaConfiguration
86 extends AbstractLogEnabled
87 implements ArchivaConfiguration, RegistryListener, Initializable
90 * Plexus registry to read the configuration from.
92 * @plexus.requirement role-hint="commons-configuration"
94 private Registry registry;
97 * The configuration that has been converted.
99 private Configuration configuration;
102 * @plexus.requirement role="org.apache.maven.archiva.policies.PreDownloadPolicy"
104 private Map<String, PreDownloadPolicy> prePolicies;
107 * @plexus.requirement role="org.apache.maven.archiva.policies.PostDownloadPolicy"
109 private Map<String, PostDownloadPolicy> postPolicies;
112 * @plexus.configuration default-value="${user.home}/.m2/archiva.xml"
114 private String userConfigFilename;
117 * @plexus.configuration default-value="${appserver.base}/conf/archiva.xml"
119 private String altConfigFilename;
122 * Configuration Listeners we've registered.
124 private Set<ConfigurationListener> listeners = new HashSet<ConfigurationListener>();
127 * Registry Listeners we've registered.
129 private Set<RegistryListener> registryListeners = new HashSet<RegistryListener>();
132 * Boolean to help determine if the configuration exists as a result of pulling in
133 * the default-archiva.xml
135 private boolean isConfigurationDefaulted = false;
137 private static final String KEY = "org.apache.maven.archiva";
139 public synchronized Configuration getConfiguration()
141 if ( configuration == null )
143 configuration = load();
144 configuration = unescapeExpressions( configuration );
147 return configuration;
150 private Configuration load()
152 // TODO: should this be the same as section? make sure unnamed sections still work (eg, sys properties)
153 Registry subset = registry.getSubset( KEY );
154 if ( subset.getString( "version" ) == null )
156 // a little autodetection of v1, even if version is omitted (this was previously allowed)
157 if ( subset.getSubset( "repositoryScanning" ).isEmpty() )
159 // only for empty, or v < 1
160 subset = readDefaultConfiguration();
164 Configuration config = new ConfigurationRegistryReader().read( subset );
166 if ( !config.getRepositories().isEmpty() )
168 for ( Iterator<V1RepositoryConfiguration> i = config.getRepositories().iterator(); i.hasNext(); )
170 V1RepositoryConfiguration r = i.next();
171 r.setScanned( r.isIndexed() );
173 if ( r.getUrl().startsWith( "file://" ) )
175 r.setLocation( r.getUrl().substring( 7 ) );
176 config.addManagedRepository( r );
178 else if ( r.getUrl().startsWith( "file:" ) )
180 r.setLocation( r.getUrl().substring( 5 ) );
181 config.addManagedRepository( r );
185 RemoteRepositoryConfiguration repo = new RemoteRepositoryConfiguration();
186 repo.setId( r.getId() );
187 repo.setLayout( r.getLayout() );
188 repo.setName( r.getName() );
189 repo.setUrl( r.getUrl() );
190 config.addRemoteRepository( repo );
194 // Prevent duplicate repositories from showing up.
195 config.getRepositories().clear();
197 registry.removeSubset( KEY + ".repositories" );
200 if ( !CollectionUtils.isEmpty( config.getRemoteRepositories() ) )
202 List<RemoteRepositoryConfiguration> remoteRepos = config.getRemoteRepositories();
203 for ( RemoteRepositoryConfiguration repo : remoteRepos )
205 // [MRM-582] Remote Repositories with empty <username> and <password> fields shouldn't be created in configuration.
206 if( StringUtils.isBlank( repo.getUsername() ) )
208 repo.setUsername( null );
211 if( StringUtils.isBlank( repo.getPassword() ) )
213 repo.setPassword( null );
218 if ( !config.getProxyConnectors().isEmpty() )
220 // Fix Proxy Connector Settings.
222 List<ProxyConnectorConfiguration> proxyConnectorList = new ArrayList<ProxyConnectorConfiguration>();
223 // Create a copy of the list to read from (to prevent concurrent modification exceptions)
224 proxyConnectorList.addAll( config.getProxyConnectors() );
225 // Remove the old connector list.
226 config.getProxyConnectors().clear();
228 for ( ProxyConnectorConfiguration connector : proxyConnectorList )
231 boolean connectorValid = true;
233 Map<String, String> policies = new HashMap<String, String>();
234 // Make copy of policies
235 policies.putAll( connector.getPolicies() );
236 // Clear out policies
237 connector.getPolicies().clear();
239 // Work thru policies. cleaning them up.
240 for ( Entry<String, String> entry : policies.entrySet() )
242 String policyId = entry.getKey();
243 String setting = entry.getValue();
245 // Upgrade old policy settings.
246 if ( "releases".equals( policyId ) || "snapshots".equals( policyId ) )
248 if ( "ignored".equals( setting ) )
250 setting = AbstractUpdatePolicy.ALWAYS;
252 else if ( "disabled".equals( setting ) )
254 setting = AbstractUpdatePolicy.NEVER;
257 else if ( "cache-failures".equals( policyId ) )
259 if ( "ignored".equals( setting ) )
261 setting = CachedFailuresPolicy.NO;
263 else if ( "cached".equals( setting ) )
265 setting = CachedFailuresPolicy.YES;
268 else if ( "checksum".equals( policyId ) )
270 if ( "ignored".equals( setting ) )
272 setting = ChecksumPolicy.IGNORE;
276 // Validate existance of policy key.
277 if ( policyExists( policyId ) )
279 DownloadPolicy policy = findPolicy( policyId );
280 // Does option exist?
281 if ( !policy.getOptions().contains( setting ) )
283 setting = policy.getDefaultOption();
285 connector.addPolicy( policyId, setting );
289 // Policy key doesn't exist. Don't add it to golden version.
290 getLogger().warn( "Policy [" + policyId + "] does not exist." );
294 if ( connectorValid )
296 config.addProxyConnector( connector );
300 // Normalize the order fields in the proxy connectors.
301 Map<String, java.util.List<ProxyConnectorConfiguration>> proxyConnectorMap = config
302 .getProxyConnectorAsMap();
304 for ( String key : proxyConnectorMap.keySet() )
306 List<ProxyConnectorConfiguration> connectors = proxyConnectorMap.get( key );
307 // Sort connectors by order field.
308 Collections.sort( connectors, ProxyConnectorConfigurationOrderComparator.getInstance() );
310 // Normalize the order field values.
312 for ( ProxyConnectorConfiguration connector : connectors )
314 connector.setOrder( order++ );
322 private DownloadPolicy findPolicy( String policyId )
324 if ( MapUtils.isEmpty( prePolicies ) )
326 getLogger().error( "No PreDownloadPolicies found!" );
330 if ( MapUtils.isEmpty( postPolicies ) )
332 getLogger().error( "No PostDownloadPolicies found!" );
336 DownloadPolicy policy;
338 policy = prePolicies.get( policyId );
339 if ( policy != null )
344 policy = postPolicies.get( policyId );
345 if ( policy != null )
353 private boolean policyExists( String policyId )
355 if ( MapUtils.isEmpty( prePolicies ) )
357 getLogger().error( "No PreDownloadPolicies found!" );
361 if ( MapUtils.isEmpty( postPolicies ) )
363 getLogger().error( "No PostDownloadPolicies found!" );
367 return ( prePolicies.containsKey( policyId ) || postPolicies.containsKey( policyId ) );
370 private Registry readDefaultConfiguration()
372 // if it contains some old configuration, remove it (Archiva 0.9)
373 registry.removeSubset( KEY );
377 registry.addConfigurationFromResource( "org/apache/maven/archiva/configuration/default-archiva.xml", KEY );
378 this.isConfigurationDefaulted = true;
380 catch ( RegistryException e )
382 throw new ConfigurationRuntimeException(
383 "Fatal error: Unable to find the built-in default configuration and load it into the registry",
386 return registry.getSubset( KEY );
389 public synchronized void save( Configuration configuration )
390 throws RegistryException, IndeterminateConfigurationException
392 Registry section = registry.getSection( KEY + ".user" );
393 Registry baseSection = registry.getSection( KEY + ".base" );
394 if ( section == null )
396 section = baseSection;
397 if ( section == null )
399 section = createDefaultConfigurationFile();
402 else if ( baseSection != null )
404 Collection<String> keys = baseSection.getKeys();
405 boolean foundList = false;
406 for ( Iterator<String> i = keys.iterator(); i.hasNext() && !foundList; )
408 String key = i.next();
410 // a little aggressive with the repositoryScanning and databaseScanning - should be no need to split
411 // that configuration
412 if ( key.startsWith( "repositories" ) || key.startsWith( "proxyConnectors" )
413 || key.startsWith( "networkProxies" ) || key.startsWith( "repositoryScanning" )
414 || key.startsWith( "databaseScanning" ) || key.startsWith( "remoteRepositories" )
415 || key.startsWith( "managedRepositories" ) )
423 this.configuration = null;
425 throw new IndeterminateConfigurationException(
426 "Configuration can not be saved when it is loaded from two sources" );
430 // escape all cron expressions to handle ','
431 escapeCronExpressions( configuration );
433 // [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.
434 if ( configuration.getManagedRepositories().isEmpty() )
436 section.removeSubset( "managedRepositories" );
438 if ( configuration.getRemoteRepositories().isEmpty() )
440 section.removeSubset( "remoteRepositories" );
442 if ( configuration.getProxyConnectors().isEmpty() )
444 section.removeSubset( "proxyConnectors" );
446 if ( configuration.getNetworkProxies().isEmpty() )
448 section.removeSubset( "networkProxies" );
450 if ( configuration.getLegacyArtifactPaths().isEmpty() )
452 section.removeSubset( "legacyArtifactPaths" );
454 if ( configuration.getRepositoryScanning() != null )
456 if ( configuration.getRepositoryScanning().getKnownContentConsumers().isEmpty() )
458 section.removeSubset( "repositoryScanning.knownContentConsumers" );
460 if ( configuration.getRepositoryScanning().getInvalidContentConsumers().isEmpty() )
462 section.removeSubset( "repositoryScanning.invalidContentConsumers" );
465 if ( configuration.getDatabaseScanning() != null )
467 if ( configuration.getDatabaseScanning().getCleanupConsumers().isEmpty() )
469 section.removeSubset( "databaseScanning.cleanupConsumers" );
471 if ( configuration.getDatabaseScanning().getUnprocessedConsumers().isEmpty() )
473 section.removeSubset( "databaseScanning.unprocessedConsumers" );
477 new ConfigurationRegistryWriter().write( configuration, section );
480 this.configuration = unescapeExpressions( configuration );
482 triggerEvent( ConfigurationEvent.SAVED );
485 private void escapeCronExpressions( Configuration configuration )
487 for ( ManagedRepositoryConfiguration c : (List<ManagedRepositoryConfiguration>) configuration.getManagedRepositories() )
489 c.setRefreshCronExpression( escapeCronExpression( c.getRefreshCronExpression() ) );
492 DatabaseScanningConfiguration scanning = configuration.getDatabaseScanning();
493 if ( scanning != null )
495 scanning.setCronExpression( escapeCronExpression( scanning.getCronExpression() ) );
499 private Registry createDefaultConfigurationFile()
500 throws RegistryException
502 // TODO: may not be needed under commons-configuration 1.4 - check
503 // UPDATE: Upgrading to commons-configuration 1.4 breaks half the unit tests. 2007-10-11 (joakime)
505 String contents = "<configuration />";
506 if ( !writeFile( "user configuration", userConfigFilename, contents ) )
508 if ( !writeFile( "alternative configuration", altConfigFilename, contents ) )
510 throw new RegistryException( "Unable to create configuration file in either user ["
511 + userConfigFilename + "] or alternative [" + altConfigFilename
512 + "] locations on disk, usually happens when not allowed to write to those locations." );
518 ( (Initializable) registry ).initialize();
520 for ( RegistryListener regListener : registryListeners )
522 addRegistryChangeListener( regListener );
525 catch ( InitializationException e )
527 throw new RegistryException( "Unable to reinitialize configuration: " + e.getMessage(), e );
530 triggerEvent( ConfigurationEvent.SAVED );
532 return registry.getSection( KEY + ".user" );
536 * Attempts to write the contents to a file, if an IOException occurs, return false.
538 * @param filetype the filetype (freeform text) to use in logging messages when failure to write.
539 * @param path the path to write to.
540 * @param contents the contents to write.
541 * @return true if write successful.
543 private boolean writeFile( String filetype, String path, String contents )
545 File file = new File( path );
549 FileUtils.writeStringToFile( file, contents, "UTF-8" );
552 catch ( IOException e )
554 getLogger().error( "Unable to create " + filetype + " file: " + e.getMessage(), e );
559 private void triggerEvent( int type )
561 ConfigurationEvent evt = new ConfigurationEvent( type );
562 for ( ConfigurationListener listener : listeners )
564 listener.configurationEvent( evt );
568 public void addListener( ConfigurationListener listener )
570 if ( listener == null )
575 listeners.add( listener );
578 public void removeListener( ConfigurationListener listener )
580 if ( listener == null )
585 listeners.remove( listener );
588 public void addChangeListener( RegistryListener listener )
590 addRegistryChangeListener( listener );
592 // keep track for later
593 registryListeners.add( listener );
596 private void addRegistryChangeListener( RegistryListener listener )
598 Registry section = registry.getSection( KEY + ".user" );
599 if ( section != null )
601 section.addChangeListener( listener );
603 section = registry.getSection( KEY + ".base" );
604 if ( section != null )
606 section.addChangeListener( listener );
610 public void initialize()
611 throws InitializationException
613 // Resolve expressions in the userConfigFilename and altConfigFilename
616 ExpressionEvaluator expressionEvaluator = new DefaultExpressionEvaluator();
617 expressionEvaluator.addExpressionSource( new SystemPropertyExpressionSource() );
618 userConfigFilename = expressionEvaluator.expand( userConfigFilename );
619 altConfigFilename = expressionEvaluator.expand( altConfigFilename );
621 catch ( EvaluatorException e )
623 throw new InitializationException( "Unable to evaluate expressions found in "
624 + "userConfigFilename or altConfigFilename." );
627 registry.addChangeListener( this );
630 public void beforeConfigurationChange( Registry registry, String propertyName, Object propertyValue )
632 // nothing to do here
635 public synchronized void afterConfigurationChange( Registry registry, String propertyName, Object propertyValue )
637 configuration = null;
640 private String removeExpressions( String directory )
642 String value = StringUtils.replace( directory, "${appserver.base}", registry.getString( "appserver.base",
643 "${appserver.base}" ) );
644 value = StringUtils.replace( value, "${appserver.home}", registry.getString( "appserver.home",
645 "${appserver.home}" ) );
649 private String unescapeCronExpression( String cronExpression )
651 return StringUtils.replace( cronExpression, "\\,", "," );
654 private String escapeCronExpression( String cronExpression )
656 return StringUtils.replace( cronExpression, ",", "\\," );
659 private Configuration unescapeExpressions( Configuration config )
661 // TODO: for commons-configuration 1.3 only
662 for ( Iterator<ManagedRepositoryConfiguration> i = config.getManagedRepositories().iterator(); i.hasNext(); )
664 ManagedRepositoryConfiguration c = i.next();
665 c.setLocation( removeExpressions( c.getLocation() ) );
666 c.setRefreshCronExpression( unescapeCronExpression( c.getRefreshCronExpression() ) );
669 DatabaseScanningConfiguration databaseScanning = config.getDatabaseScanning();
670 if ( databaseScanning != null )
672 String cron = databaseScanning.getCronExpression();
673 databaseScanning.setCronExpression( unescapeCronExpression( cron ) );
679 public String getUserConfigFilename()
681 return userConfigFilename;
684 public String getAltConfigFilename()
686 return altConfigFilename;
689 public boolean isDefaulted()
691 return this.isConfigurationDefaulted;