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