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;
57 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"
105 * @todo these don't strictly belong in here
107 private Map<String, PreDownloadPolicy> prePolicies;
110 * @plexus.requirement role="org.apache.maven.archiva.policies.PostDownloadPolicy"
111 * @todo these don't strictly belong in here
113 private Map<String, PostDownloadPolicy> postPolicies;
116 * @plexus.configuration default-value="${user.home}/.m2/archiva.xml"
118 private String userConfigFilename;
121 * @plexus.configuration default-value="${appserver.base}/conf/archiva.xml"
123 private String altConfigFilename;
126 * Configuration Listeners we've registered.
128 private Set<ConfigurationListener> listeners = new HashSet<ConfigurationListener>();
131 * Registry Listeners we've registered.
133 private Set<RegistryListener> registryListeners = new HashSet<RegistryListener>();
136 * Boolean to help determine if the configuration exists as a result of pulling in
137 * the default-archiva.xml
139 private boolean isConfigurationDefaulted = false;
141 private static final String KEY = "org.apache.maven.archiva";
143 public synchronized Configuration getConfiguration()
145 if ( configuration == null )
147 configuration = load();
148 configuration = unescapeExpressions( configuration );
151 return configuration;
154 private Configuration load()
156 // TODO: should this be the same as section? make sure unnamed sections still work (eg, sys properties)
157 Registry subset = registry.getSubset( KEY );
158 if ( subset.getString( "version" ) == null )
160 // a little autodetection of v1, even if version is omitted (this was previously allowed)
161 if ( subset.getSubset( "repositoryScanning" ).isEmpty() )
163 // only for empty, or v < 1
164 subset = readDefaultConfiguration();
168 Configuration config = new ConfigurationRegistryReader().read( subset );
170 if ( !config.getRepositories().isEmpty() )
172 for ( Iterator<V1RepositoryConfiguration> i = config.getRepositories().iterator(); i.hasNext(); )
174 V1RepositoryConfiguration r = i.next();
175 r.setScanned( r.isIndexed() );
177 if ( r.getUrl().startsWith( "file://" ) )
179 r.setLocation( r.getUrl().substring( 7 ) );
180 config.addManagedRepository( r );
182 else if ( r.getUrl().startsWith( "file:" ) )
184 r.setLocation( r.getUrl().substring( 5 ) );
185 config.addManagedRepository( r );
189 RemoteRepositoryConfiguration repo = new RemoteRepositoryConfiguration();
190 repo.setId( r.getId() );
191 repo.setLayout( r.getLayout() );
192 repo.setName( r.getName() );
193 repo.setUrl( r.getUrl() );
194 config.addRemoteRepository( repo );
198 // Prevent duplicate repositories from showing up.
199 config.getRepositories().clear();
201 registry.removeSubset( KEY + ".repositories" );
204 if ( !CollectionUtils.isEmpty( config.getRemoteRepositories() ) )
206 List<RemoteRepositoryConfiguration> remoteRepos = config.getRemoteRepositories();
207 for ( RemoteRepositoryConfiguration repo : remoteRepos )
209 // [MRM-582] Remote Repositories with empty <username> and <password> fields shouldn't be created in configuration.
210 if ( StringUtils.isBlank( repo.getUsername() ) )
212 repo.setUsername( null );
215 if ( StringUtils.isBlank( repo.getPassword() ) )
217 repo.setPassword( null );
222 if ( !config.getProxyConnectors().isEmpty() )
224 // Fix Proxy Connector Settings.
226 List<ProxyConnectorConfiguration> proxyConnectorList = new ArrayList<ProxyConnectorConfiguration>();
227 // Create a copy of the list to read from (to prevent concurrent modification exceptions)
228 proxyConnectorList.addAll( config.getProxyConnectors() );
229 // Remove the old connector list.
230 config.getProxyConnectors().clear();
232 for ( ProxyConnectorConfiguration connector : proxyConnectorList )
235 boolean connectorValid = true;
237 Map<String, String> policies = new HashMap<String, String>();
238 // Make copy of policies
239 policies.putAll( connector.getPolicies() );
240 // Clear out policies
241 connector.getPolicies().clear();
243 // Work thru policies. cleaning them up.
244 for ( Entry<String, String> entry : policies.entrySet() )
246 String policyId = entry.getKey();
247 String setting = entry.getValue();
249 // Upgrade old policy settings.
250 if ( "releases".equals( policyId ) || "snapshots".equals( policyId ) )
252 if ( "ignored".equals( setting ) )
254 setting = AbstractUpdatePolicy.ALWAYS;
256 else if ( "disabled".equals( setting ) )
258 setting = AbstractUpdatePolicy.NEVER;
261 else if ( "cache-failures".equals( policyId ) )
263 if ( "ignored".equals( setting ) )
265 setting = CachedFailuresPolicy.NO;
267 else if ( "cached".equals( setting ) )
269 setting = CachedFailuresPolicy.YES;
272 else if ( "checksum".equals( policyId ) )
274 if ( "ignored".equals( setting ) )
276 setting = ChecksumPolicy.IGNORE;
280 // Validate existance of policy key.
281 if ( policyExists( policyId ) )
283 DownloadPolicy policy = findPolicy( policyId );
284 // Does option exist?
285 if ( !policy.getOptions().contains( setting ) )
287 setting = policy.getDefaultOption();
289 connector.addPolicy( policyId, setting );
293 // Policy key doesn't exist. Don't add it to golden version.
294 log.warn( "Policy [" + policyId + "] does not exist." );
298 if ( connectorValid )
300 config.addProxyConnector( connector );
304 // Normalize the order fields in the proxy connectors.
305 Map<String, java.util.List<ProxyConnectorConfiguration>> proxyConnectorMap = config
306 .getProxyConnectorAsMap();
308 for ( String key : proxyConnectorMap.keySet() )
310 List<ProxyConnectorConfiguration> connectors = proxyConnectorMap.get( key );
311 // Sort connectors by order field.
312 Collections.sort( connectors, ProxyConnectorConfigurationOrderComparator.getInstance() );
314 // Normalize the order field values.
316 for ( ProxyConnectorConfiguration connector : connectors )
318 connector.setOrder( order++ );
326 private DownloadPolicy findPolicy( String policyId )
328 if ( MapUtils.isEmpty( prePolicies ) )
330 log.error( "No PreDownloadPolicies found!" );
334 if ( MapUtils.isEmpty( postPolicies ) )
336 log.error( "No PostDownloadPolicies found!" );
340 DownloadPolicy policy;
342 policy = prePolicies.get( policyId );
343 if ( policy != null )
348 policy = postPolicies.get( policyId );
349 if ( policy != null )
357 private boolean policyExists( String policyId )
359 if ( MapUtils.isEmpty( prePolicies ) )
361 log.error( "No PreDownloadPolicies found!" );
365 if ( MapUtils.isEmpty( postPolicies ) )
367 log.error( "No PostDownloadPolicies found!" );
371 return ( prePolicies.containsKey( policyId ) || postPolicies.containsKey( policyId ) );
374 private Registry readDefaultConfiguration()
376 // if it contains some old configuration, remove it (Archiva 0.9)
377 registry.removeSubset( KEY );
381 registry.addConfigurationFromResource( "org/apache/maven/archiva/configuration/default-archiva.xml", KEY );
382 this.isConfigurationDefaulted = true;
384 catch ( RegistryException e )
386 throw new ConfigurationRuntimeException(
387 "Fatal error: Unable to find the built-in default configuration and load it into the registry",
390 return registry.getSubset( KEY );
393 public synchronized void save( Configuration configuration )
394 throws RegistryException, IndeterminateConfigurationException
396 Registry section = registry.getSection( KEY + ".user" );
397 Registry baseSection = registry.getSection( KEY + ".base" );
398 if ( section == null )
400 section = baseSection;
401 if ( section == null )
403 section = createDefaultConfigurationFile();
406 else if ( baseSection != null )
408 Collection<String> keys = baseSection.getKeys();
409 boolean foundList = false;
410 for ( Iterator<String> i = keys.iterator(); i.hasNext() && !foundList; )
412 String key = i.next();
414 // a little aggressive with the repositoryScanning and databaseScanning - should be no need to split
415 // that configuration
416 if ( key.startsWith( "repositories" ) || key.startsWith( "proxyConnectors" )
417 || key.startsWith( "networkProxies" ) || key.startsWith( "repositoryScanning" )
418 || key.startsWith( "databaseScanning" ) || key.startsWith( "remoteRepositories" )
419 || key.startsWith( "managedRepositories" ) )
427 this.configuration = null;
429 throw new IndeterminateConfigurationException(
430 "Configuration can not be saved when it is loaded from two sources" );
434 // escape all cron expressions to handle ','
435 escapeCronExpressions( configuration );
437 // [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.
438 if ( configuration.getManagedRepositories().isEmpty() )
440 section.removeSubset( "managedRepositories" );
442 if ( configuration.getRemoteRepositories().isEmpty() )
444 section.removeSubset( "remoteRepositories" );
446 if ( configuration.getProxyConnectors().isEmpty() )
448 section.removeSubset( "proxyConnectors" );
450 if ( configuration.getNetworkProxies().isEmpty() )
452 section.removeSubset( "networkProxies" );
454 if ( configuration.getLegacyArtifactPaths().isEmpty() )
456 section.removeSubset( "legacyArtifactPaths" );
458 if ( configuration.getRepositoryScanning() != null )
460 if ( configuration.getRepositoryScanning().getKnownContentConsumers().isEmpty() )
462 section.removeSubset( "repositoryScanning.knownContentConsumers" );
464 if ( configuration.getRepositoryScanning().getInvalidContentConsumers().isEmpty() )
466 section.removeSubset( "repositoryScanning.invalidContentConsumers" );
469 if ( configuration.getDatabaseScanning() != null )
471 if ( configuration.getDatabaseScanning().getCleanupConsumers().isEmpty() )
473 section.removeSubset( "databaseScanning.cleanupConsumers" );
475 if ( configuration.getDatabaseScanning().getUnprocessedConsumers().isEmpty() )
477 section.removeSubset( "databaseScanning.unprocessedConsumers" );
481 new ConfigurationRegistryWriter().write( configuration, section );
484 this.configuration = unescapeExpressions( configuration );
486 triggerEvent( ConfigurationEvent.SAVED );
489 private void escapeCronExpressions( Configuration configuration )
491 for ( ManagedRepositoryConfiguration c : (List<ManagedRepositoryConfiguration>) configuration.getManagedRepositories() )
493 c.setRefreshCronExpression( escapeCronExpression( c.getRefreshCronExpression() ) );
496 DatabaseScanningConfiguration scanning = configuration.getDatabaseScanning();
497 if ( scanning != null )
499 scanning.setCronExpression( escapeCronExpression( scanning.getCronExpression() ) );
503 private Registry createDefaultConfigurationFile()
504 throws RegistryException
506 // TODO: may not be needed under commons-configuration 1.4 - check
507 // UPDATE: Upgrading to commons-configuration 1.4 breaks half the unit tests. 2007-10-11 (joakime)
509 String contents = "<configuration />";
510 if ( !writeFile( "user configuration", userConfigFilename, contents ) )
512 if ( !writeFile( "alternative configuration", altConfigFilename, contents ) )
514 throw new RegistryException( "Unable to create configuration file in either user ["
515 + userConfigFilename + "] or alternative [" + altConfigFilename
516 + "] locations on disk, usually happens when not allowed to write to those locations." );
522 ( (Initializable) registry ).initialize();
524 for ( RegistryListener regListener : registryListeners )
526 addRegistryChangeListener( regListener );
529 catch ( InitializationException e )
531 throw new RegistryException( "Unable to reinitialize configuration: " + e.getMessage(), e );
534 triggerEvent( ConfigurationEvent.SAVED );
536 return registry.getSection( KEY + ".user" );
540 * Attempts to write the contents to a file, if an IOException occurs, return false.
542 * The file will be created if the directory to the file exists, otherwise this will return false.
544 * @param filetype the filetype (freeform text) to use in logging messages when failure to write.
545 * @param path the path to write to.
546 * @param contents the contents to write.
547 * @return true if write successful.
549 private boolean writeFile( String filetype, String path, String contents )
551 File file = new File( path );
555 // Check parent directory (if it is declared)
556 if ( file.getParentFile() != null )
558 // Check that directory exists
559 if ( ( file.getParentFile().exists() == false ) || ( file.getParentFile().isDirectory() == false ) )
561 // Directory to file must exist for file to be created
566 FileUtils.writeStringToFile( file, contents, "UTF-8" );
569 catch ( IOException e )
571 log.error( "Unable to create " + filetype + " file: " + e.getMessage(), e );
576 private void triggerEvent( int type )
578 ConfigurationEvent evt = new ConfigurationEvent( type );
579 for ( ConfigurationListener listener : listeners )
581 listener.configurationEvent( evt );
585 public void addListener( ConfigurationListener listener )
587 if ( listener == null )
592 listeners.add( listener );
595 public void removeListener( ConfigurationListener listener )
597 if ( listener == null )
602 listeners.remove( listener );
605 public void addChangeListener( RegistryListener listener )
607 addRegistryChangeListener( listener );
609 // keep track for later
610 registryListeners.add( listener );
613 private void addRegistryChangeListener( RegistryListener listener )
615 Registry section = registry.getSection( KEY + ".user" );
616 if ( section != null )
618 section.addChangeListener( listener );
620 section = registry.getSection( KEY + ".base" );
621 if ( section != null )
623 section.addChangeListener( listener );
627 public void initialize()
628 throws InitializationException
630 // Resolve expressions in the userConfigFilename and altConfigFilename
633 ExpressionEvaluator expressionEvaluator = new DefaultExpressionEvaluator();
634 expressionEvaluator.addExpressionSource( new SystemPropertyExpressionSource() );
635 userConfigFilename = expressionEvaluator.expand( userConfigFilename );
636 altConfigFilename = expressionEvaluator.expand( altConfigFilename );
638 catch ( EvaluatorException e )
640 throw new InitializationException( "Unable to evaluate expressions found in "
641 + "userConfigFilename or altConfigFilename." );
644 registry.addChangeListener( this );
647 public void beforeConfigurationChange( Registry registry, String propertyName, Object propertyValue )
649 // nothing to do here
652 public synchronized void afterConfigurationChange( Registry registry, String propertyName, Object propertyValue )
654 configuration = null;
657 private String removeExpressions( String directory )
659 String value = StringUtils.replace( directory, "${appserver.base}", registry.getString( "appserver.base",
660 "${appserver.base}" ) );
661 value = StringUtils.replace( value, "${appserver.home}", registry.getString( "appserver.home",
662 "${appserver.home}" ) );
666 private String unescapeCronExpression( String cronExpression )
668 return StringUtils.replace( cronExpression, "\\,", "," );
671 private String escapeCronExpression( String cronExpression )
673 return StringUtils.replace( cronExpression, ",", "\\," );
676 private Configuration unescapeExpressions( Configuration config )
678 // TODO: for commons-configuration 1.3 only
679 for ( Iterator<ManagedRepositoryConfiguration> i = config.getManagedRepositories().iterator(); i.hasNext(); )
681 ManagedRepositoryConfiguration c = i.next();
682 c.setLocation( removeExpressions( c.getLocation() ) );
683 c.setRefreshCronExpression( unescapeCronExpression( c.getRefreshCronExpression() ) );
686 DatabaseScanningConfiguration databaseScanning = config.getDatabaseScanning();
687 if ( databaseScanning != null )
689 String cron = databaseScanning.getCronExpression();
690 databaseScanning.setCronExpression( unescapeCronExpression( cron ) );
696 public String getUserConfigFilename()
698 return userConfigFilename;
701 public String getAltConfigFilename()
703 return altConfigFilename;
706 public boolean isDefaulted()
708 return this.isConfigurationDefaulted;