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