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.Policy;
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 Configuration getConfiguration()
145 return loadConfiguration();
148 private synchronized Configuration loadConfiguration()
150 if ( configuration == null )
152 configuration = load();
153 configuration = unescapeExpressions( configuration );
154 if( isConfigurationDefaulted )
156 configuration = checkRepositoryLocations( configuration );
160 return configuration;
163 private Configuration load()
165 // TODO: should this be the same as section? make sure unnamed sections still work (eg, sys properties)
166 Registry subset = registry.getSubset( KEY );
167 if ( subset.getString( "version" ) == null )
169 // a little autodetection of v1, even if version is omitted (this was previously allowed)
170 if ( subset.getSubset( "repositoryScanning" ).isEmpty() )
172 // only for empty, or v < 1
173 subset = readDefaultConfiguration();
177 Configuration config = new ConfigurationRegistryReader().read( subset );
179 if ( !config.getRepositories().isEmpty() )
181 for ( Iterator<V1RepositoryConfiguration> i = config.getRepositories().iterator(); i.hasNext(); )
183 V1RepositoryConfiguration r = i.next();
184 r.setScanned( r.isIndexed() );
186 if ( r.getUrl().startsWith( "file://" ) )
188 r.setLocation( r.getUrl().substring( 7 ) );
189 config.addManagedRepository( r );
191 else if ( r.getUrl().startsWith( "file:" ) )
193 r.setLocation( r.getUrl().substring( 5 ) );
194 config.addManagedRepository( r );
198 RemoteRepositoryConfiguration repo = new RemoteRepositoryConfiguration();
199 repo.setId( r.getId() );
200 repo.setLayout( r.getLayout() );
201 repo.setName( r.getName() );
202 repo.setUrl( r.getUrl() );
203 config.addRemoteRepository( repo );
207 // Prevent duplicate repositories from showing up.
208 config.getRepositories().clear();
210 registry.removeSubset( KEY + ".repositories" );
213 if ( !CollectionUtils.isEmpty( config.getRemoteRepositories() ) )
215 List<RemoteRepositoryConfiguration> remoteRepos = config.getRemoteRepositories();
216 for ( RemoteRepositoryConfiguration repo : remoteRepos )
218 // [MRM-582] Remote Repositories with empty <username> and <password> fields shouldn't be created in configuration.
219 if ( StringUtils.isBlank( repo.getUsername() ) )
221 repo.setUsername( null );
224 if ( StringUtils.isBlank( repo.getPassword() ) )
226 repo.setPassword( null );
231 if ( !config.getProxyConnectors().isEmpty() )
233 // Fix Proxy Connector Settings.
235 List<ProxyConnectorConfiguration> proxyConnectorList = new ArrayList<ProxyConnectorConfiguration>();
236 // Create a copy of the list to read from (to prevent concurrent modification exceptions)
237 proxyConnectorList.addAll( config.getProxyConnectors() );
238 // Remove the old connector list.
239 config.getProxyConnectors().clear();
241 for ( ProxyConnectorConfiguration connector : proxyConnectorList )
244 boolean connectorValid = true;
246 Map<String, String> policies = new HashMap<String, String>();
247 // Make copy of policies
248 policies.putAll( connector.getPolicies() );
249 // Clear out policies
250 connector.getPolicies().clear();
252 // Work thru policies. cleaning them up.
253 for ( Entry<String, String> entry : policies.entrySet() )
255 String policyId = entry.getKey();
256 String setting = entry.getValue();
258 // Upgrade old policy settings.
259 if ( "releases".equals( policyId ) || "snapshots".equals( policyId ) )
261 if ( "ignored".equals( setting ) )
263 setting = AbstractUpdatePolicy.ALWAYS;
265 else if ( "disabled".equals( setting ) )
267 setting = AbstractUpdatePolicy.NEVER;
270 else if ( "cache-failures".equals( policyId ) )
272 if ( "ignored".equals( setting ) )
274 setting = CachedFailuresPolicy.NO;
276 else if ( "cached".equals( setting ) )
278 setting = CachedFailuresPolicy.YES;
281 else if ( "checksum".equals( policyId ) )
283 if ( "ignored".equals( setting ) )
285 setting = ChecksumPolicy.IGNORE;
289 // Validate existance of policy key.
290 if ( policyExists( policyId ) )
292 Policy policy = findPolicy( policyId );
293 // Does option exist?
294 if ( !policy.getOptions().contains( setting ) )
296 setting = policy.getDefaultOption();
298 connector.addPolicy( policyId, setting );
302 // Policy key doesn't exist. Don't add it to golden version.
303 log.warn( "Policy [" + policyId + "] does not exist." );
307 if ( connectorValid )
309 config.addProxyConnector( connector );
313 // Normalize the order fields in the proxy connectors.
314 Map<String, java.util.List<ProxyConnectorConfiguration>> proxyConnectorMap = config
315 .getProxyConnectorAsMap();
317 for ( String key : proxyConnectorMap.keySet() )
319 List<ProxyConnectorConfiguration> connectors = proxyConnectorMap.get( key );
320 // Sort connectors by order field.
321 Collections.sort( connectors, ProxyConnectorConfigurationOrderComparator.getInstance() );
323 // Normalize the order field values.
325 for ( ProxyConnectorConfiguration connector : connectors )
327 connector.setOrder( order++ );
335 private Policy findPolicy( String policyId )
337 if ( MapUtils.isEmpty( prePolicies ) )
339 log.error( "No PreDownloadPolicies found!" );
343 if ( MapUtils.isEmpty( postPolicies ) )
345 log.error( "No PostDownloadPolicies found!" );
351 policy = prePolicies.get( policyId );
352 if ( policy != null )
357 policy = postPolicies.get( policyId );
358 if ( policy != null )
366 private boolean policyExists( String policyId )
368 if ( MapUtils.isEmpty( prePolicies ) )
370 log.error( "No PreDownloadPolicies found!" );
374 if ( MapUtils.isEmpty( postPolicies ) )
376 log.error( "No PostDownloadPolicies found!" );
380 return ( prePolicies.containsKey( policyId ) || postPolicies.containsKey( policyId ) );
383 private Registry readDefaultConfiguration()
385 // if it contains some old configuration, remove it (Archiva 0.9)
386 registry.removeSubset( KEY );
390 registry.addConfigurationFromResource( "org/apache/maven/archiva/configuration/default-archiva.xml", KEY );
391 this.isConfigurationDefaulted = true;
393 catch ( RegistryException e )
395 throw new ConfigurationRuntimeException(
396 "Fatal error: Unable to find the built-in default configuration and load it into the registry",
399 return registry.getSubset( KEY );
402 public synchronized void save( Configuration configuration )
403 throws RegistryException, IndeterminateConfigurationException
405 Registry section = registry.getSection( KEY + ".user" );
406 Registry baseSection = registry.getSection( KEY + ".base" );
407 if ( section == null )
409 section = baseSection;
410 if ( section == null )
412 section = createDefaultConfigurationFile();
415 else if ( baseSection != null )
417 Collection<String> keys = baseSection.getKeys();
418 boolean foundList = false;
419 for ( Iterator<String> i = keys.iterator(); i.hasNext() && !foundList; )
421 String key = i.next();
423 // a little aggressive with the repositoryScanning and databaseScanning - should be no need to split
424 // that configuration
425 if ( key.startsWith( "repositories" ) || key.startsWith( "proxyConnectors" )
426 || key.startsWith( "networkProxies" ) || key.startsWith( "repositoryScanning" )
427 || key.startsWith( "databaseScanning" ) || key.startsWith( "remoteRepositories" )
428 || key.startsWith( "managedRepositories" ) || key.startsWith( "repositoryGroups" ) )
436 this.configuration = null;
438 throw new IndeterminateConfigurationException(
439 "Configuration can not be saved when it is loaded from two sources" );
443 // escape all cron expressions to handle ','
444 escapeCronExpressions( configuration );
446 // [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.
447 if ( configuration.getManagedRepositories().isEmpty() )
449 section.removeSubset( "managedRepositories" );
451 if ( configuration.getRemoteRepositories().isEmpty() )
453 section.removeSubset( "remoteRepositories" );
455 if ( configuration.getProxyConnectors().isEmpty() )
457 section.removeSubset( "proxyConnectors" );
459 if ( configuration.getNetworkProxies().isEmpty() )
461 section.removeSubset( "networkProxies" );
463 if ( configuration.getLegacyArtifactPaths().isEmpty() )
465 section.removeSubset( "legacyArtifactPaths" );
467 if ( configuration.getRepositoryGroups().isEmpty() )
469 section.removeSubset( "repositoryGroups" );
471 if ( configuration.getRepositoryScanning() != null )
473 if ( configuration.getRepositoryScanning().getKnownContentConsumers().isEmpty() )
475 section.removeSubset( "repositoryScanning.knownContentConsumers" );
477 if ( configuration.getRepositoryScanning().getInvalidContentConsumers().isEmpty() )
479 section.removeSubset( "repositoryScanning.invalidContentConsumers" );
482 if ( configuration.getDatabaseScanning() != null )
484 if ( configuration.getDatabaseScanning().getCleanupConsumers().isEmpty() )
486 section.removeSubset( "databaseScanning.cleanupConsumers" );
488 if ( configuration.getDatabaseScanning().getUnprocessedConsumers().isEmpty() )
490 section.removeSubset( "databaseScanning.unprocessedConsumers" );
494 new ConfigurationRegistryWriter().write( configuration, section );
497 this.configuration = unescapeExpressions( configuration );
499 triggerEvent( ConfigurationEvent.SAVED );
502 private void escapeCronExpressions( Configuration configuration )
504 for ( ManagedRepositoryConfiguration c : (List<ManagedRepositoryConfiguration>) configuration.getManagedRepositories() )
506 c.setRefreshCronExpression( escapeCronExpression( c.getRefreshCronExpression() ) );
509 DatabaseScanningConfiguration scanning = configuration.getDatabaseScanning();
510 if ( scanning != null )
512 scanning.setCronExpression( escapeCronExpression( scanning.getCronExpression() ) );
516 private Registry createDefaultConfigurationFile()
517 throws RegistryException
519 // TODO: may not be needed under commons-configuration 1.4 - check
520 // UPDATE: Upgrading to commons-configuration 1.4 breaks half the unit tests. 2007-10-11 (joakime)
522 String contents = "<configuration />";
523 if ( !writeFile( "user configuration", userConfigFilename, contents ) )
525 if ( !writeFile( "alternative configuration", altConfigFilename, contents ) )
527 throw new RegistryException( "Unable to create configuration file in either user ["
528 + userConfigFilename + "] or alternative [" + altConfigFilename
529 + "] locations on disk, usually happens when not allowed to write to those locations." );
535 ( (Initializable) registry ).initialize();
537 for ( RegistryListener regListener : registryListeners )
539 addRegistryChangeListener( regListener );
542 catch ( InitializationException e )
544 throw new RegistryException( "Unable to reinitialize configuration: " + e.getMessage(), e );
547 triggerEvent( ConfigurationEvent.SAVED );
549 return registry.getSection( KEY + ".user" );
553 * Attempts to write the contents to a file, if an IOException occurs, return false.
555 * The file will be created if the directory to the file exists, otherwise this will return false.
557 * @param filetype the filetype (freeform text) to use in logging messages when failure to write.
558 * @param path the path to write to.
559 * @param contents the contents to write.
560 * @return true if write successful.
562 private boolean writeFile( String filetype, String path, String contents )
564 File file = new File( path );
568 // Check parent directory (if it is declared)
569 if ( file.getParentFile() != null )
571 // Check that directory exists
572 if ( ( file.getParentFile().exists() == false ) || ( file.getParentFile().isDirectory() == false ) )
574 // Directory to file must exist for file to be created
579 FileUtils.writeStringToFile( file, contents, "UTF-8" );
582 catch ( IOException e )
584 log.error( "Unable to create " + filetype + " file: " + e.getMessage(), e );
589 private void triggerEvent( int type )
591 ConfigurationEvent evt = new ConfigurationEvent( type );
592 for ( ConfigurationListener listener : listeners )
594 listener.configurationEvent( evt );
598 public void addListener( ConfigurationListener listener )
600 if ( listener == null )
605 listeners.add( listener );
608 public void removeListener( ConfigurationListener listener )
610 if ( listener == null )
615 listeners.remove( listener );
618 public void addChangeListener( RegistryListener listener )
620 addRegistryChangeListener( listener );
622 // keep track for later
623 registryListeners.add( listener );
626 private void addRegistryChangeListener( RegistryListener listener )
628 Registry section = registry.getSection( KEY + ".user" );
629 if ( section != null )
631 section.addChangeListener( listener );
633 section = registry.getSection( KEY + ".base" );
634 if ( section != null )
636 section.addChangeListener( listener );
640 public void initialize()
641 throws InitializationException
643 // Resolve expressions in the userConfigFilename and altConfigFilename
646 ExpressionEvaluator expressionEvaluator = new DefaultExpressionEvaluator();
647 expressionEvaluator.addExpressionSource( new SystemPropertyExpressionSource() );
648 userConfigFilename = expressionEvaluator.expand( userConfigFilename );
649 altConfigFilename = expressionEvaluator.expand( altConfigFilename );
652 catch ( EvaluatorException e )
654 throw new InitializationException( "Unable to evaluate expressions found in "
655 + "userConfigFilename or altConfigFilename." );
657 registry.addChangeListener( this );
660 public void beforeConfigurationChange( Registry registry, String propertyName, Object propertyValue )
662 // nothing to do here
665 public synchronized void afterConfigurationChange( Registry registry, String propertyName, Object propertyValue )
667 configuration = null;
670 private String removeExpressions( String directory )
672 String value = StringUtils.replace( directory, "${appserver.base}", registry.getString( "appserver.base",
673 "${appserver.base}" ) );
674 value = StringUtils.replace( value, "${appserver.home}", registry.getString( "appserver.home",
675 "${appserver.home}" ) );
679 private String unescapeCronExpression( String cronExpression )
681 return StringUtils.replace( cronExpression, "\\,", "," );
684 private String escapeCronExpression( String cronExpression )
686 return StringUtils.replace( cronExpression, ",", "\\," );
689 private Configuration unescapeExpressions( Configuration config )
691 // TODO: for commons-configuration 1.3 only
692 for ( Iterator<ManagedRepositoryConfiguration> i = config.getManagedRepositories().iterator(); i.hasNext(); )
694 ManagedRepositoryConfiguration c = i.next();
695 c.setLocation( removeExpressions( c.getLocation() ) );
696 c.setRefreshCronExpression( unescapeCronExpression( c.getRefreshCronExpression() ) );
699 DatabaseScanningConfiguration databaseScanning = config.getDatabaseScanning();
700 if ( databaseScanning != null )
702 String cron = databaseScanning.getCronExpression();
703 databaseScanning.setCronExpression( unescapeCronExpression( cron ) );
709 private Configuration checkRepositoryLocations( Configuration config )
711 // additional check for [MRM-789], ensure that the location of the default repositories
712 // are not installed in the server installation
713 for( ManagedRepositoryConfiguration repo : (List<ManagedRepositoryConfiguration>) config.getManagedRepositories() )
715 String repoPath = repo.getLocation();
716 File repoLocation = new File( repoPath );
718 if( repoLocation.exists() && repoLocation.isDirectory() && !repoPath.endsWith( "data/repositories/" + repo.getId() ) )
720 repo.setLocation( repoPath + "/data/repositories/" + repo.getId() );
727 public String getUserConfigFilename()
729 return userConfigFilename;
732 public String getAltConfigFilename()
734 return altConfigFilename;
737 public boolean isDefaulted()
739 return this.isConfigurationDefaulted;