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