]> source.dussan.org Git - archiva.git/blob
987a456d728c23d6275144b4375b01a946645a38
[archiva.git] /
1 package org.apache.maven.archiva.configuration;
2
3 /*
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
11  *
12  *   http://www.apache.org/licenses/LICENSE-2.0
13  *
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
19  * under the License.
20  */
21
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;
46
47 import java.io.File;
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;
56 import java.util.Map;
57 import java.util.Set;
58 import java.util.Map.Entry;
59
60 /**
61  * <p>
62  * Implementation of configuration holder that retrieves it from the registry.
63  * </p>
64  * <p>
65  * The registry layers and merges the 2 configuration files: user, and application server.
66  * </p>
67  * <p>
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.
71  * </p>
72  * <p>
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.
78  * </p>
79  * <p>
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.
82  * </p>
83  *
84  * @plexus.component role="org.apache.maven.archiva.configuration.ArchivaConfiguration"
85  */
86 public class DefaultArchivaConfiguration
87     implements ArchivaConfiguration, RegistryListener, Initializable
88 {
89     private Logger log = LoggerFactory.getLogger(DefaultArchivaConfiguration.class);
90     
91     /**
92      * Plexus registry to read the configuration from.
93      *
94      * @plexus.requirement role-hint="commons-configuration"
95      */
96     private Registry registry;
97
98     /**
99      * The configuration that has been converted.
100      */
101     private Configuration configuration;
102
103     /**
104      * @plexus.requirement role="org.apache.maven.archiva.policies.PreDownloadPolicy"
105      */
106     private Map<String, PreDownloadPolicy> prePolicies;
107
108     /**
109      * @plexus.requirement role="org.apache.maven.archiva.policies.PostDownloadPolicy"
110      */
111     private Map<String, PostDownloadPolicy> postPolicies;
112     
113     /**
114      * @plexus.configuration default-value="${user.home}/.m2/archiva.xml"
115      */
116     private String userConfigFilename;
117
118     /**
119      * @plexus.configuration default-value="${appserver.base}/conf/archiva.xml"
120      */
121     private String altConfigFilename;
122
123     /**
124      * Configuration Listeners we've registered.
125      */
126     private Set<ConfigurationListener> listeners = new HashSet<ConfigurationListener>();
127
128     /**
129      * Registry Listeners we've registered.
130      */
131     private Set<RegistryListener> registryListeners = new HashSet<RegistryListener>();
132     
133     /**
134      * Boolean to help determine if the configuration exists as a result of pulling in
135      * the default-archiva.xml
136      */
137     private boolean isConfigurationDefaulted = false;
138
139     private static final String KEY = "org.apache.maven.archiva";
140
141     public synchronized Configuration getConfiguration()
142     {
143         if ( configuration == null )
144         {
145             configuration = load();
146             configuration = processExpressions( configuration );
147         }
148
149         return configuration;
150     }
151
152     private Configuration load()
153     {
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 )
157         {
158             // a little autodetection of v1, even if version is omitted (this was previously allowed)
159             if ( subset.getSubset( "repositoryScanning" ).isEmpty() )
160             {
161                 // only for empty, or v < 1
162                 subset = readDefaultConfiguration();
163             }
164         }
165
166         Configuration config = new ConfigurationRegistryReader().read( subset );
167
168         if ( !config.getRepositories().isEmpty() )
169         {
170             for ( Iterator<V1RepositoryConfiguration> i = config.getRepositories().iterator(); i.hasNext(); )
171             {
172                 V1RepositoryConfiguration r = i.next();
173                 r.setScanned( r.isIndexed() );
174
175                 if ( r.getUrl().startsWith( "file://" ) )
176                 {
177                     r.setLocation( r.getUrl().substring( 7 ) );
178                     config.addManagedRepository( r );
179                 }
180                 else if ( r.getUrl().startsWith( "file:" ) )
181                 {
182                     r.setLocation( r.getUrl().substring( 5 ) );
183                     config.addManagedRepository( r );
184                 }
185                 else
186                 {
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 );
193                 }
194             }
195
196             // Prevent duplicate repositories from showing up.
197             config.getRepositories().clear();
198
199             registry.removeSubset( KEY + ".repositories" );
200         }
201         
202         if ( !CollectionUtils.isEmpty( config.getRemoteRepositories() ) )
203         {
204             List<RemoteRepositoryConfiguration> remoteRepos = config.getRemoteRepositories();
205             for ( RemoteRepositoryConfiguration repo : remoteRepos )
206             {
207                 // [MRM-582] Remote Repositories with empty <username> and <password> fields shouldn't be created in configuration.
208                 if( StringUtils.isBlank( repo.getUsername() ) )
209                 {
210                     repo.setUsername( null );
211                 }
212                 
213                 if( StringUtils.isBlank( repo.getPassword() ) )
214                 {
215                     repo.setPassword( null );
216                 }
217             }
218         }
219
220         if ( !config.getProxyConnectors().isEmpty() )
221         {
222             // Fix Proxy Connector Settings.
223
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();
229
230             for ( ProxyConnectorConfiguration connector : proxyConnectorList )
231             {
232                 // Fix policies
233                 boolean connectorValid = true;
234
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();
240
241                 // Work thru policies. cleaning them up.
242                 for ( Entry<String, String> entry : policies.entrySet() )
243                 {
244                     String policyId = entry.getKey();
245                     String setting = entry.getValue();
246
247                     // Upgrade old policy settings.
248                     if ( "releases".equals( policyId ) || "snapshots".equals( policyId ) )
249                     {
250                         if ( "ignored".equals( setting ) )
251                         {
252                             setting = AbstractUpdatePolicy.ALWAYS;
253                         }
254                         else if ( "disabled".equals( setting ) )
255                         {
256                             setting = AbstractUpdatePolicy.NEVER;
257                         }
258                     }
259                     else if ( "cache-failures".equals( policyId ) )
260                     {
261                         if ( "ignored".equals( setting ) )
262                         {
263                             setting = CachedFailuresPolicy.NO;
264                         }
265                         else if ( "cached".equals( setting ) )
266                         {
267                             setting = CachedFailuresPolicy.YES;
268                         }
269                     }
270                     else if ( "checksum".equals( policyId ) )
271                     {
272                         if ( "ignored".equals( setting ) )
273                         {
274                             setting = ChecksumPolicy.IGNORE;
275                         }
276                     }
277
278                     // Validate existance of policy key.
279                     if ( policyExists( policyId ) )
280                     {
281                         DownloadPolicy policy = findPolicy( policyId );
282                         // Does option exist?
283                         if ( !policy.getOptions().contains( setting ) )
284                         {
285                             setting = policy.getDefaultOption();
286                         }
287                         connector.addPolicy( policyId, setting );
288                     }
289                     else
290                     {
291                         // Policy key doesn't exist. Don't add it to golden version.
292                         log.warn( "Policy [" + policyId + "] does not exist." );
293                     }
294                 }
295
296                 if ( connectorValid )
297                 {
298                     config.addProxyConnector( connector );
299                 }
300             }
301
302             // Normalize the order fields in the proxy connectors.
303             Map<String, java.util.List<ProxyConnectorConfiguration>> proxyConnectorMap = config
304                 .getProxyConnectorAsMap();
305
306             for ( String key : proxyConnectorMap.keySet() )
307             {
308                 List<ProxyConnectorConfiguration> connectors = proxyConnectorMap.get( key );
309                 // Sort connectors by order field.
310                 Collections.sort( connectors, ProxyConnectorConfigurationOrderComparator.getInstance() );
311
312                 // Normalize the order field values.
313                 int order = 1;
314                 for ( ProxyConnectorConfiguration connector : connectors )
315                 {
316                     connector.setOrder( order++ );
317                 }
318             }
319         }
320
321         return config;
322     }
323
324     private DownloadPolicy findPolicy( String policyId )
325     {
326         if ( MapUtils.isEmpty( prePolicies ) )
327         {
328             log.error( "No PreDownloadPolicies found!" );
329             return null;
330         }
331
332         if ( MapUtils.isEmpty( postPolicies ) )
333         {
334             log.error( "No PostDownloadPolicies found!" );
335             return null;
336         }
337
338         DownloadPolicy policy;
339
340         policy = prePolicies.get( policyId );
341         if ( policy != null )
342         {
343             return policy;
344         }
345
346         policy = postPolicies.get( policyId );
347         if ( policy != null )
348         {
349             return policy;
350         }
351
352         return null;
353     }
354
355     private boolean policyExists( String policyId )
356     {
357         if ( MapUtils.isEmpty( prePolicies ) )
358         {
359             log.error( "No PreDownloadPolicies found!" );
360             return false;
361         }
362         
363         if ( MapUtils.isEmpty( postPolicies ) )
364         {
365             log.error( "No PostDownloadPolicies found!" );
366             return false;
367         }
368         
369         return ( prePolicies.containsKey( policyId ) || postPolicies.containsKey( policyId ) );
370     }
371
372     private Registry readDefaultConfiguration()
373     {
374         // if it contains some old configuration, remove it (Archiva 0.9)
375         registry.removeSubset( KEY );
376
377         try
378         {
379             registry.addConfigurationFromResource( "org/apache/maven/archiva/configuration/default-archiva.xml", KEY );
380             this.isConfigurationDefaulted = true;
381         }
382         catch ( RegistryException e )
383         {
384             throw new ConfigurationRuntimeException(
385                                                      "Fatal error: Unable to find the built-in default configuration and load it into the registry",
386                                                      e );
387         }
388         return registry.getSubset( KEY );
389     }
390
391     public synchronized void save( Configuration configuration )
392         throws RegistryException, IndeterminateConfigurationException
393     {
394         Registry section = registry.getSection( KEY + ".user" );
395         Registry baseSection = registry.getSection( KEY + ".base" );
396         if ( section == null )
397         {
398             section = baseSection;
399             if ( section == null )
400             {
401                 section = createDefaultConfigurationFile();
402             }
403         }
404         else if ( baseSection != null )
405         {
406             Collection<String> keys = baseSection.getKeys();
407             boolean foundList = false;
408             for ( Iterator<String> i = keys.iterator(); i.hasNext() && !foundList; )
409             {
410                 String key = i.next();
411
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" ) )
418                 {
419                     foundList = true;
420                 }
421             }
422
423             if ( foundList )
424             {
425                 this.configuration = null;
426
427                 throw new IndeterminateConfigurationException(
428                                                                "Configuration can not be saved when it is loaded from two sources" );
429             }
430         }
431
432         // escape all cron expressions to handle ','
433         for ( Iterator<ManagedRepositoryConfiguration> i = configuration.getManagedRepositories().iterator(); i
434             .hasNext(); )
435         {
436             ManagedRepositoryConfiguration c = i.next();
437             c.setRefreshCronExpression( escapeCronExpression( c.getRefreshCronExpression() ) );
438         }
439
440         if ( configuration.getDatabaseScanning() != null )
441         {
442             configuration.getDatabaseScanning().setCronExpression(
443                                                                    escapeCronExpression( configuration
444                                                                        .getDatabaseScanning().getCronExpression() ) );
445         }
446
447         new ConfigurationRegistryWriter().write( configuration, section );
448         section.save();
449
450         triggerEvent( ConfigurationEvent.SAVED );
451
452         this.configuration = processExpressions( configuration );
453     }
454
455     private Registry createDefaultConfigurationFile()
456         throws RegistryException
457     {
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)
460
461         String contents = "<configuration />";
462         if ( !writeFile( "user configuration", userConfigFilename, contents ) )
463         {
464             if ( !writeFile( "alternative configuration", altConfigFilename, contents ) )
465             {
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." );
469             }
470         }
471
472         try
473         {
474             ( (Initializable) registry ).initialize();
475
476             for ( RegistryListener regListener : registryListeners )
477             {
478                 addRegistryChangeListener( regListener );
479             }
480         }
481         catch ( InitializationException e )
482         {
483             throw new RegistryException( "Unable to reinitialize configuration: " + e.getMessage(), e );
484         }
485
486         triggerEvent( ConfigurationEvent.SAVED );
487
488         return registry.getSection( KEY + ".user" );
489     }
490
491     /**
492      * Attempts to write the contents to a file, if an IOException occurs, return false.
493      * 
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.
498      */
499     private boolean writeFile( String filetype, String path, String contents )
500     {
501         File file = new File( path );
502
503         try
504         {
505             FileUtils.writeStringToFile( file, contents, "UTF-8" );
506             return true;
507         }
508         catch ( IOException e )
509         {
510             log.error( "Unable to create " + filetype + " file: " + e.getMessage(), e );
511             return false;
512         }
513     }
514
515     private void triggerEvent( int type )
516     {
517         ConfigurationEvent evt = new ConfigurationEvent( type );
518         for ( ConfigurationListener listener : listeners )
519         {
520             try
521             {
522                 listener.configurationEvent( evt );
523             }
524             catch ( Throwable t )
525             {
526                 log.warn( "Unable to notify of saved configuration event.", t );
527             }
528         }
529     }
530
531     public void addListener( ConfigurationListener listener )
532     {
533         if ( listener == null )
534         {
535             return;
536         }
537
538         listeners.add( listener );
539     }
540
541     public void removeListener( ConfigurationListener listener )
542     {
543         if ( listener == null )
544         {
545             return;
546         }
547
548         listeners.remove( listener );
549     }
550
551     public void addChangeListener( RegistryListener listener )
552     {
553         addRegistryChangeListener( listener );
554
555         // keep track for later
556         registryListeners.add( listener );
557     }
558
559     private void addRegistryChangeListener( RegistryListener listener )
560     {
561         Registry section = registry.getSection( KEY + ".user" );
562         if ( section != null )
563         {
564             section.addChangeListener( listener );
565         }
566         section = registry.getSection( KEY + ".base" );
567         if ( section != null )
568         {
569             section.addChangeListener( listener );
570         }
571     }
572
573     public void initialize()
574         throws InitializationException
575     {
576         // Resolve expressions in the userConfigFilename and altConfigFilename
577         try
578         {
579             ExpressionEvaluator expressionEvaluator = new DefaultExpressionEvaluator();
580             expressionEvaluator.addExpressionSource( new SystemPropertyExpressionSource() );
581             userConfigFilename = expressionEvaluator.expand( userConfigFilename );
582             altConfigFilename = expressionEvaluator.expand( altConfigFilename );
583         }
584         catch ( EvaluatorException e )
585         {
586             throw new InitializationException( "Unable to evaluate expressions found in "
587                 + "userConfigFilename or altConfigFilename." );
588         }
589
590         registry.addChangeListener( this );
591     }
592
593     public void beforeConfigurationChange( Registry registry, String propertyName, Object propertyValue )
594     {
595         // nothing to do here
596     }
597
598     public synchronized void afterConfigurationChange( Registry registry, String propertyName, Object propertyValue )
599     {
600         configuration = null;
601     }
602
603     private String removeExpressions( String directory )
604     {
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}" ) );
609         return value;
610     }
611
612     private String unescapeCronExpression( String cronExpression )
613     {
614         return StringUtils.replace( cronExpression, "\\,", "," );
615     }
616
617     private String escapeCronExpression( String cronExpression )
618     {
619         return StringUtils.replace( cronExpression, ",", "\\," );
620     }
621
622     private Configuration processExpressions( Configuration config )
623     {
624         // TODO: for commons-configuration 1.3 only
625         for ( Iterator<ManagedRepositoryConfiguration> i = config.getManagedRepositories().iterator(); i.hasNext(); )
626         {
627             ManagedRepositoryConfiguration c = i.next();
628             c.setLocation( removeExpressions( c.getLocation() ) );
629             c.setRefreshCronExpression( unescapeCronExpression( c.getRefreshCronExpression() ) );
630         }
631
632         DatabaseScanningConfiguration databaseScanning = config.getDatabaseScanning();
633         if ( databaseScanning != null )
634         {
635             String cron = databaseScanning.getCronExpression();
636             databaseScanning.setCronExpression( unescapeCronExpression( cron ) );
637         }
638
639         return config;
640     }
641
642     public String getUserConfigFilename()
643     {
644         return userConfigFilename;
645     }
646
647     public String getAltConfigFilename()
648     {
649         return altConfigFilename;
650     }
651
652     public boolean isDefaulted()
653     {
654         return this.isConfigurationDefaulted;
655     }
656 }