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.configuration.BaseConfiguration;
25 import org.apache.commons.io.FileUtils;
26 import org.apache.commons.lang.StringUtils;
27 import org.apache.maven.archiva.configuration.functors.ProxyConnectorConfigurationOrderComparator;
28 import org.apache.maven.archiva.configuration.io.registry.ConfigurationRegistryReader;
29 import org.apache.maven.archiva.configuration.io.registry.ConfigurationRegistryWriter;
30 import org.apache.maven.archiva.policies.AbstractUpdatePolicy;
31 import org.apache.maven.archiva.policies.CachedFailuresPolicy;
32 import org.apache.maven.archiva.policies.ChecksumPolicy;
33 import org.apache.maven.archiva.policies.Policy;
34 import org.apache.maven.archiva.policies.PostDownloadPolicy;
35 import org.apache.maven.archiva.policies.PreDownloadPolicy;
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.Collection;
56 import java.util.Collections;
57 import java.util.HashMap;
58 import java.util.HashSet;
59 import java.util.Iterator;
60 import java.util.List;
62 import java.util.Map.Entry;
67 * Implementation of configuration holder that retrieves it from the registry.
70 * The registry layers and merges the 2 configuration files: user, and application server.
73 * Instead of relying on the model defaults, if the registry is empty a default configuration file is loaded and
74 * applied from a resource. The defaults are not loaded into the registry as the lists (eg repositories) could no longer
75 * be removed if that was the case.
78 * When saving the configuration, it is saved to the location it was read from. If it was read from the defaults, it
79 * will be saved to the user location.
80 * However, if the configuration contains information from both sources, an exception is raised as this is currently
81 * unsupported. The reason for this is that it is not possible to identify where to re-save elements, and can result
82 * in list configurations (eg repositories) becoming inconsistent.
85 * If the configuration is outdated, it will be upgraded when it is loaded. This is done by checking the version flag
86 * before reading it from the registry.
89 * plexus.component role="org.apache.maven.archiva.configuration.ArchivaConfiguration"
91 @Service( "archivaConfiguration#default" )
92 public class DefaultArchivaConfiguration
93 implements ArchivaConfiguration, RegistryListener
95 private Logger log = LoggerFactory.getLogger( DefaultArchivaConfiguration.class );
98 * Plexus registry to read the configuration from.
100 * plexus.requirement role-hint="commons-configuration"
103 @Named( value = "commons-configuration" )
104 private Registry registry;
107 private ComponentContainer componentContainer;
110 * The configuration that has been converted.
112 private Configuration configuration;
116 * plexus.requirement role="org.apache.maven.archiva.policies.PreDownloadPolicy"
118 * @todo these don't strictly belong in here
120 private Map<String, PreDownloadPolicy> prePolicies;
124 * plexus.requirement role="org.apache.maven.archiva.policies.PostDownloadPolicy"
126 * @todo these don't strictly belong in here
128 private Map<String, PostDownloadPolicy> postPolicies;
132 * plexus.configuration default-value="${user.home}/.m2/archiva.xml"
134 private String userConfigFilename = "${user.home}/.m2/archiva.xml";
138 * plexus.configuration default-value="${appserver.base}/conf/archiva.xml"
140 private String altConfigFilename = "${appserver.base}/conf/archiva.xml";
143 * Configuration Listeners we've registered.
145 private Set<ConfigurationListener> listeners = new HashSet<ConfigurationListener>();
148 * Registry Listeners we've registered.
150 private Set<RegistryListener> registryListeners = new HashSet<RegistryListener>();
153 * Boolean to help determine if the configuration exists as a result of pulling in
154 * the default-archiva.xml
156 private boolean isConfigurationDefaulted = false;
158 private static final String KEY = "org.apache.maven.archiva";
160 public Configuration getConfiguration()
162 return loadConfiguration();
165 private synchronized Configuration loadConfiguration()
167 if ( configuration == null )
169 configuration = load();
170 configuration = unescapeExpressions( configuration );
171 if ( isConfigurationDefaulted )
173 configuration = checkRepositoryLocations( configuration );
177 return configuration;
180 @SuppressWarnings( "unchecked" )
181 private Configuration load()
183 // TODO: should this be the same as section? make sure unnamed sections still work (eg, sys properties)
184 Registry subset = registry.getSubset( KEY );
185 if ( subset.getString( "version" ) == null )
187 // a little autodetection of v1, even if version is omitted (this was previously allowed)
188 if ( subset.getSubset( "repositoryScanning" ).isEmpty() )
190 // only for empty, or v < 1
191 subset = readDefaultConfiguration();
195 Configuration config = new ConfigurationRegistryReader().read( subset );
197 config.getRepositoryGroups();
198 config.getRepositoryGroupsAsMap();
199 if ( !config.getRepositories().isEmpty() )
201 for ( Iterator<V1RepositoryConfiguration> i = config.getRepositories().iterator(); i.hasNext(); )
203 V1RepositoryConfiguration r = i.next();
204 r.setScanned( r.isIndexed() );
206 if ( r.getUrl().startsWith( "file://" ) )
208 r.setLocation( r.getUrl().substring( 7 ) );
209 config.addManagedRepository( r );
211 else if ( r.getUrl().startsWith( "file:" ) )
213 r.setLocation( r.getUrl().substring( 5 ) );
214 config.addManagedRepository( r );
218 RemoteRepositoryConfiguration repo = new RemoteRepositoryConfiguration();
219 repo.setId( r.getId() );
220 repo.setLayout( r.getLayout() );
221 repo.setName( r.getName() );
222 repo.setUrl( r.getUrl() );
223 config.addRemoteRepository( repo );
227 // Prevent duplicate repositories from showing up.
228 config.getRepositories().clear();
230 registry.removeSubset( KEY + ".repositories" );
233 if ( !CollectionUtils.isEmpty( config.getRemoteRepositories() ) )
235 List<RemoteRepositoryConfiguration> remoteRepos = config.getRemoteRepositories();
236 for ( RemoteRepositoryConfiguration repo : remoteRepos )
238 // [MRM-582] Remote Repositories with empty <username> and <password> fields shouldn't be created in configuration.
239 if ( StringUtils.isBlank( repo.getUsername() ) )
241 repo.setUsername( null );
244 if ( StringUtils.isBlank( repo.getPassword() ) )
246 repo.setPassword( null );
251 if ( !config.getProxyConnectors().isEmpty() )
253 // Fix Proxy Connector Settings.
255 List<ProxyConnectorConfiguration> proxyConnectorList = new ArrayList<ProxyConnectorConfiguration>();
256 // Create a copy of the list to read from (to prevent concurrent modification exceptions)
257 proxyConnectorList.addAll( config.getProxyConnectors() );
258 // Remove the old connector list.
259 config.getProxyConnectors().clear();
261 for ( ProxyConnectorConfiguration connector : proxyConnectorList )
264 boolean connectorValid = true;
266 Map<String, String> policies = new HashMap<String, String>();
267 // Make copy of policies
268 policies.putAll( connector.getPolicies() );
269 // Clear out policies
270 connector.getPolicies().clear();
272 // Work thru policies. cleaning them up.
273 for ( Entry<String, String> entry : policies.entrySet() )
275 String policyId = entry.getKey();
276 String setting = entry.getValue();
278 // Upgrade old policy settings.
279 if ( "releases".equals( policyId ) || "snapshots".equals( policyId ) )
281 if ( "ignored".equals( setting ) )
283 setting = AbstractUpdatePolicy.ALWAYS;
285 else if ( "disabled".equals( setting ) )
287 setting = AbstractUpdatePolicy.NEVER;
290 else if ( "cache-failures".equals( policyId ) )
292 if ( "ignored".equals( setting ) )
294 setting = CachedFailuresPolicy.NO;
296 else if ( "cached".equals( setting ) )
298 setting = CachedFailuresPolicy.YES;
301 else if ( "checksum".equals( policyId ) )
303 if ( "ignored".equals( setting ) )
305 setting = ChecksumPolicy.IGNORE;
309 // Validate existance of policy key.
310 if ( policyExists( policyId ) )
312 Policy policy = findPolicy( policyId );
313 // Does option exist?
314 if ( !policy.getOptions().contains( setting ) )
316 setting = policy.getDefaultOption();
318 connector.addPolicy( policyId, setting );
322 // Policy key doesn't exist. Don't add it to golden version.
323 log.warn( "Policy [" + policyId + "] does not exist." );
327 if ( connectorValid )
329 config.addProxyConnector( connector );
333 // Normalize the order fields in the proxy connectors.
334 Map<String, java.util.List<ProxyConnectorConfiguration>> proxyConnectorMap =
335 config.getProxyConnectorAsMap();
337 for ( List<ProxyConnectorConfiguration> connectors : proxyConnectorMap.values() )
339 // Sort connectors by order field.
340 Collections.sort( connectors, ProxyConnectorConfigurationOrderComparator.getInstance() );
342 // Normalize the order field values.
344 for ( ProxyConnectorConfiguration connector : connectors )
346 connector.setOrder( order++ );
354 private Policy findPolicy( String policyId )
356 if ( MapUtils.isEmpty( prePolicies ) )
358 log.error( "No PreDownloadPolicies found!" );
362 if ( MapUtils.isEmpty( postPolicies ) )
364 log.error( "No PostDownloadPolicies found!" );
370 policy = prePolicies.get( policyId );
371 if ( policy != null )
376 policy = postPolicies.get( policyId );
377 if ( policy != null )
385 private boolean policyExists( String policyId )
387 if ( MapUtils.isEmpty( prePolicies ) )
389 log.error( "No PreDownloadPolicies found!" );
393 if ( MapUtils.isEmpty( postPolicies ) )
395 log.error( "No PostDownloadPolicies found!" );
399 return ( prePolicies.containsKey( policyId ) || postPolicies.containsKey( policyId ) );
402 private Registry readDefaultConfiguration()
404 // if it contains some old configuration, remove it (Archiva 0.9)
405 registry.removeSubset( KEY );
409 registry.addConfigurationFromResource( "org/apache/maven/archiva/configuration/default-archiva.xml", KEY );
410 this.isConfigurationDefaulted = true;
412 catch ( RegistryException e )
414 throw new ConfigurationRuntimeException(
415 "Fatal error: Unable to find the built-in default configuration and load it into the registry", e );
417 return registry.getSubset( KEY );
420 @SuppressWarnings( "unchecked" )
421 public synchronized void save( Configuration configuration )
422 throws IndeterminateConfigurationException, RegistryException
424 Registry section = registry.getSection( KEY + ".user" );
425 Registry baseSection = registry.getSection( KEY + ".base" );
426 if ( section == null )
428 section = baseSection;
429 if ( section == null )
431 section = createDefaultConfigurationFile();
434 else if ( baseSection != null )
436 Collection<String> keys = baseSection.getKeys();
437 boolean foundList = false;
438 for ( Iterator<String> i = keys.iterator(); i.hasNext() && !foundList; )
440 String key = i.next();
442 // a little aggressive with the repositoryScanning and databaseScanning - should be no need to split
443 // that configuration
444 if ( key.startsWith( "repositories" ) || key.startsWith( "proxyConnectors" )
445 || key.startsWith( "networkProxies" ) || key.startsWith( "repositoryScanning" )
446 || key.startsWith( "databaseScanning" ) || key.startsWith( "remoteRepositories" )
447 || key.startsWith( "managedRepositories" ) || key.startsWith( "repositoryGroups" ) )
455 this.configuration = null;
457 throw new IndeterminateConfigurationException(
458 "Configuration can not be saved when it is loaded from two sources" );
462 // escape all cron expressions to handle ','
463 escapeCronExpressions( configuration );
465 // [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.
466 if ( configuration.getManagedRepositories().isEmpty() && section != null )
468 section.removeSubset( "managedRepositories" );
470 if ( configuration.getRemoteRepositories().isEmpty() && section != null )
472 section.removeSubset( "remoteRepositories" );
475 if ( configuration.getProxyConnectors().isEmpty() && section != null )
477 section.removeSubset( "proxyConnectors" );
479 if ( configuration.getNetworkProxies().isEmpty() && section != null )
481 section.removeSubset( "networkProxies" );
483 if ( configuration.getLegacyArtifactPaths().isEmpty() && section != null )
485 section.removeSubset( "legacyArtifactPaths" );
487 if ( configuration.getRepositoryGroups().isEmpty() && section != null )
489 section.removeSubset( "repositoryGroups" );
491 if ( configuration.getRepositoryScanning() != null )
493 if ( configuration.getRepositoryScanning().getKnownContentConsumers().isEmpty() && section != null )
495 section.removeSubset( "repositoryScanning.knownContentConsumers" );
497 if ( configuration.getRepositoryScanning().getInvalidContentConsumers().isEmpty() && section != null )
499 section.removeSubset( "repositoryScanning.invalidContentConsumers" );
502 if ( configuration.getDatabaseScanning() != null )
504 if ( configuration.getDatabaseScanning().getCleanupConsumers().isEmpty() && section != null )
506 section.removeSubset( "databaseScanning.cleanupConsumers" );
509 if ( configuration.getDatabaseScanning().getUnprocessedConsumers().isEmpty() && section != null )
511 section.removeSubset( "databaseScanning.unprocessedConsumers" );
515 new ConfigurationRegistryWriter().write( configuration, section );
518 this.configuration = unescapeExpressions( configuration );
520 triggerEvent( ConfigurationEvent.SAVED );
523 private void escapeCronExpressions( Configuration configuration )
525 for ( ManagedRepositoryConfiguration c : (List<ManagedRepositoryConfiguration>) configuration.getManagedRepositories() )
527 c.setRefreshCronExpression( escapeCronExpression( c.getRefreshCronExpression() ) );
530 DatabaseScanningConfiguration scanning = configuration.getDatabaseScanning();
531 if ( scanning != null )
533 scanning.setCronExpression( escapeCronExpression( scanning.getCronExpression() ) );
537 private Registry createDefaultConfigurationFile()
538 throws RegistryException
540 // TODO: may not be needed under commons-configuration 1.4 - check
541 // UPDATE: Upgrading to commons-configuration 1.4 breaks half the unit tests. 2007-10-11 (joakime)
543 String contents = "<configuration />";
545 String fileLocation = userConfigFilename;
547 if ( !writeFile( "user configuration", userConfigFilename, contents ) )
549 fileLocation = altConfigFilename;
550 if ( !writeFile( "alternative configuration", altConfigFilename, contents ) )
552 throw new RegistryException(
553 "Unable to create configuration file in either user [" + userConfigFilename + "] or alternative ["
555 + "] locations on disk, usually happens when not allowed to write to those locations." );
559 // olamy hackish I know :-)
560 contents = "<configuration><xml fileName=\"" + fileLocation +"\" config-forceCreate=\"true\" config-name=\"org.apache.maven.archiva.user\"/>"
561 + "</configuration>";
564 ( (CommonsConfigurationRegistry) registry ).setProperties( contents );
566 ( (CommonsConfigurationRegistry) registry ).initialize();
568 for ( RegistryListener regListener : registryListeners )
570 addRegistryChangeListener( regListener );
573 triggerEvent( ConfigurationEvent.SAVED );
575 Registry section = registry.getSection( KEY + ".user" );
576 return section == null ? new CommonsConfigurationRegistry( new BaseConfiguration() ) : section;
580 * Attempts to write the contents to a file, if an IOException occurs, return false.
582 * The file will be created if the directory to the file exists, otherwise this will return false.
584 * @param filetype the filetype (freeform text) to use in logging messages when failure to write.
585 * @param path the path to write to.
586 * @param contents the contents to write.
587 * @return true if write successful.
589 private boolean writeFile( String filetype, String path, String contents )
591 File file = new File( path );
595 // Check parent directory (if it is declared)
596 if ( file.getParentFile() != null )
598 // Check that directory exists
599 if ( ( file.getParentFile().exists() == false ) || ( file.getParentFile().isDirectory() == false ) )
601 // Directory to file must exist for file to be created
606 FileUtils.writeStringToFile( file, contents, "UTF-8" );
609 catch ( IOException e )
611 log.error( "Unable to create " + filetype + " file: " + e.getMessage(), e );
616 private void triggerEvent( int type )
618 ConfigurationEvent evt = new ConfigurationEvent( type );
619 for ( ConfigurationListener listener : listeners )
621 listener.configurationEvent( evt );
625 public void addListener( ConfigurationListener listener )
627 if ( listener == null )
632 listeners.add( listener );
635 public void removeListener( ConfigurationListener listener )
637 if ( listener == null )
642 listeners.remove( listener );
645 public void addChangeListener( RegistryListener listener )
647 addRegistryChangeListener( listener );
649 // keep track for later
650 registryListeners.add( listener );
653 private void addRegistryChangeListener( RegistryListener listener )
655 Registry section = registry.getSection( KEY + ".user" );
656 if ( section != null )
658 section.addChangeListener( listener );
660 section = registry.getSection( KEY + ".base" );
661 if ( section != null )
663 section.addChangeListener( listener );
668 public void initialize()
671 this.postPolicies = componentContainer.buildMapWithRole( PostDownloadPolicy.class );
672 this.prePolicies = componentContainer.buildMapWithRole( PreDownloadPolicy.class );
673 // Resolve expressions in the userConfigFilename and altConfigFilename
676 ExpressionEvaluator expressionEvaluator = new DefaultExpressionEvaluator();
677 expressionEvaluator.addExpressionSource( new SystemPropertyExpressionSource() );
678 userConfigFilename = expressionEvaluator.expand( userConfigFilename );
679 altConfigFilename = expressionEvaluator.expand( altConfigFilename );
682 catch ( EvaluatorException e )
684 throw new RuntimeException(
685 "Unable to evaluate expressions found in " + "userConfigFilename or altConfigFilename." );
687 registry.addChangeListener( this );
692 this.configuration = null;
695 this.registry.initialize();
697 catch ( RegistryException e )
699 throw new ConfigurationRuntimeException( e.getMessage(), e );
704 public void beforeConfigurationChange( Registry registry, String propertyName, Object propertyValue )
706 // nothing to do here
709 public synchronized void afterConfigurationChange( Registry registry, String propertyName, Object propertyValue )
711 configuration = null;
714 private String removeExpressions( String directory )
716 String value = StringUtils.replace( directory, "${appserver.base}",
717 registry.getString( "appserver.base", "${appserver.base}" ) );
718 value = StringUtils.replace( value, "${appserver.home}",
719 registry.getString( "appserver.home", "${appserver.home}" ) );
723 private String unescapeCronExpression( String cronExpression )
725 return StringUtils.replace( cronExpression, "\\,", "," );
728 private String escapeCronExpression( String cronExpression )
730 return StringUtils.replace( cronExpression, ",", "\\," );
733 private Configuration unescapeExpressions( Configuration config )
735 // TODO: for commons-configuration 1.3 only
736 for ( Iterator<ManagedRepositoryConfiguration> i = config.getManagedRepositories().iterator(); i.hasNext(); )
738 ManagedRepositoryConfiguration c = i.next();
739 c.setLocation( removeExpressions( c.getLocation() ) );
740 c.setRefreshCronExpression( unescapeCronExpression( c.getRefreshCronExpression() ) );
743 DatabaseScanningConfiguration databaseScanning = config.getDatabaseScanning();
744 if ( databaseScanning != null )
746 String cron = databaseScanning.getCronExpression();
747 databaseScanning.setCronExpression( unescapeCronExpression( cron ) );
753 private Configuration checkRepositoryLocations( Configuration config )
755 // additional check for [MRM-789], ensure that the location of the default repositories
756 // are not installed in the server installation
757 for ( ManagedRepositoryConfiguration repo : (List<ManagedRepositoryConfiguration>) config.getManagedRepositories() )
759 String repoPath = repo.getLocation();
760 File repoLocation = new File( repoPath );
762 if ( repoLocation.exists() && repoLocation.isDirectory() && !repoPath.endsWith(
763 "data/repositories/" + repo.getId() ) )
765 repo.setLocation( repoPath + "/data/repositories/" + repo.getId() );
772 public String getUserConfigFilename()
774 return userConfigFilename;
777 public String getAltConfigFilename()
779 return altConfigFilename;
782 public boolean isDefaulted()
784 return this.isConfigurationDefaulted;
787 public Registry getRegistry()
792 public void setRegistry( Registry registry )
794 this.registry = registry;
798 public void setUserConfigFilename( String userConfigFilename )
800 this.userConfigFilename = userConfigFilename;
803 public void setAltConfigFilename( String altConfigFilename )
805 this.altConfigFilename = altConfigFilename;