]> source.dussan.org Git - archiva.git/blob
5ca08dc85d92eed3503d1abfa74cd1429918bf26
[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.io.FileUtils;
23 import org.apache.maven.archiva.configuration.functors.ProxyConnectorConfigurationOrderComparator;
24 import org.apache.maven.archiva.configuration.io.registry.ConfigurationRegistryReader;
25 import org.apache.maven.archiva.configuration.io.registry.ConfigurationRegistryWriter;
26 import org.codehaus.plexus.evaluator.DefaultExpressionEvaluator;
27 import org.codehaus.plexus.evaluator.EvaluatorException;
28 import org.codehaus.plexus.evaluator.ExpressionEvaluator;
29 import org.codehaus.plexus.evaluator.sources.SystemPropertyExpressionSource;
30 import org.codehaus.plexus.logging.AbstractLogEnabled;
31 import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable;
32 import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException;
33 import org.codehaus.plexus.registry.Registry;
34 import org.codehaus.plexus.registry.RegistryException;
35 import org.codehaus.plexus.registry.RegistryListener;
36 import org.codehaus.plexus.util.StringUtils;
37
38 import java.io.File;
39 import java.io.IOException;
40 import java.util.Collection;
41 import java.util.Collections;
42 import java.util.HashSet;
43 import java.util.Iterator;
44 import java.util.List;
45 import java.util.Map;
46 import java.util.Set;
47
48 /**
49  * <p>
50  * Implementation of configuration holder that retrieves it from the registry.
51  * </p>
52  * <p>
53  * The registry layers and merges the 2 configuration files: user, and application server.
54  * </p>
55  * <p>
56  * Instead of relying on the model defaults, if the registry is empty a default configuration file is loaded and
57  * applied from a resource. The defaults are not loaded into the registry as the lists (eg repositories) could no longer
58  * be removed if that was the case.
59  * </p>
60  * <p>
61  * When saving the configuration, it is saved to the location it was read from. If it was read from the defaults, it
62  * will be saved to the user location.
63  * However, if the configuration contains information from both sources, an exception is raised as this is currently
64  * unsupported. The reason for this is that it is not possible to identify where to re-save elements, and can result
65  * in list configurations (eg repositories) becoming inconsistent.
66  * </p>
67  * <p>
68  * If the configuration is outdated, it will be upgraded when it is loaded. This is done by checking the version flag
69  * before reading it from the registry.
70  * </p>
71  *
72  * @plexus.component role="org.apache.maven.archiva.configuration.ArchivaConfiguration"
73  */
74 public class DefaultArchivaConfiguration
75     extends AbstractLogEnabled
76     implements ArchivaConfiguration, RegistryListener, Initializable
77 {
78     /**
79      * Plexus registry to read the configuration from.
80      *
81      * @plexus.requirement role-hint="commons-configuration"
82      */
83     private Registry registry;
84
85     /**
86      * The configuration that has been converted.
87      */
88     private Configuration configuration;
89
90     private static final String KEY = "org.apache.maven.archiva";
91
92     /**
93      * @plexus.configuration default-value="${user.home}/.m2/archiva.xml"
94      */
95     private String userConfigFilename;
96
97     /**
98      * @plexus.configuration default-value="${appserver.base}/conf/archiva.xml"
99      */
100     private String altConfigFilename;
101
102     /**
103      * Configuration Listeners we've registered.
104      */
105     private Set<ConfigurationListener> listeners = new HashSet<ConfigurationListener>();
106
107     /**
108      * Registry Listeners we've registered.
109      */
110     private Set<RegistryListener> registryListeners = new HashSet<RegistryListener>();
111     
112     /**
113      * Boolean to help determine if the configuration exists as a result of pulling in
114      * the default-archiva.xml
115      */
116     private boolean isConfigurationDefaulted = false;
117
118     public synchronized Configuration getConfiguration()
119     {
120         if ( configuration == null )
121         {
122             configuration = load();
123             configuration = processExpressions( configuration );
124         }
125
126         return configuration;
127     }
128
129     private Configuration load()
130     {
131         // TODO: should this be the same as section? make sure unnamed sections still work (eg, sys properties)
132         Registry subset = registry.getSubset( KEY );
133         if ( subset.getString( "version" ) == null )
134         {
135             // a little autodetection of v1, even if version is omitted (this was previously allowed)
136             if ( subset.getSubset( "repositoryScanning" ).isEmpty() )
137             {
138                 // only for empty, or v < 1
139                 subset = readDefaultConfiguration();
140             }
141         }
142
143         Configuration config = new ConfigurationRegistryReader().read( subset );
144
145         if ( !config.getRepositories().isEmpty() )
146         {
147             for ( Iterator<V1RepositoryConfiguration> i = config.getRepositories().iterator(); i.hasNext(); )
148             {
149                 V1RepositoryConfiguration r = i.next();
150                 r.setScanned( r.isIndexed() );
151
152                 if ( r.getUrl().startsWith( "file://" ) )
153                 {
154                     r.setLocation( r.getUrl().substring( 7 ) );
155                     config.addManagedRepository( r );
156                 }
157                 else if ( r.getUrl().startsWith( "file:" ) )
158                 {
159                     r.setLocation( r.getUrl().substring( 5 ) );
160                     config.addManagedRepository( r );
161                 }
162                 else
163                 {
164                     RemoteRepositoryConfiguration repo = new RemoteRepositoryConfiguration();
165                     repo.setId( r.getId() );
166                     repo.setLayout( r.getLayout() );
167                     repo.setName( r.getName() );
168                     repo.setUrl( r.getUrl() );
169                     config.addRemoteRepository( repo );
170                 }
171             }
172
173             // Prevent duplicate repositories from showing up.
174             config.getRepositories().clear();
175         }
176
177         // Normalize the order fields in the proxy connectors.
178         if ( !config.getProxyConnectors().isEmpty() )
179         {
180             Map<String, java.util.List<ProxyConnectorConfiguration>> proxyConnectorMap = config
181                 .getProxyConnectorAsMap();
182
183             for ( String key : proxyConnectorMap.keySet() )
184             {
185                 List<ProxyConnectorConfiguration> connectors = proxyConnectorMap.get( key );
186                 // Sort connectors by order field.
187                 Collections.sort( connectors, ProxyConnectorConfigurationOrderComparator.getInstance() );
188
189                 // Normalize the order field values.
190                 int order = 1;
191                 for ( ProxyConnectorConfiguration connector : connectors )
192                 {
193                     connector.setOrder( order++ );
194                 }
195             }
196         }
197
198         return config;
199     }
200
201     private Registry readDefaultConfiguration()
202     {
203         // if it contains some old configuration, remove it (Archiva 0.9)
204         registry.removeSubset( KEY );
205
206         try
207         {
208             registry.addConfigurationFromResource( "org/apache/maven/archiva/configuration/default-archiva.xml", KEY );
209             this.isConfigurationDefaulted = true;
210         }
211         catch ( RegistryException e )
212         {
213             throw new ConfigurationRuntimeException(
214                                                      "Fatal error: Unable to find the built-in default configuration and load it into the registry",
215                                                      e );
216         }
217         return registry.getSubset( KEY );
218     }
219
220     public synchronized void save( Configuration configuration )
221         throws RegistryException, IndeterminateConfigurationException
222     {
223         Registry section = registry.getSection( KEY + ".user" );
224         Registry baseSection = registry.getSection( KEY + ".base" );
225         if ( section == null )
226         {
227             section = baseSection;
228             if ( section == null )
229             {
230                 section = createDefaultConfigurationFile();
231             }
232         }
233         else if ( baseSection != null )
234         {
235             Collection<String> keys = baseSection.getKeys();
236             boolean foundList = false;
237             for ( Iterator<String> i = keys.iterator(); i.hasNext() && !foundList; )
238             {
239                 String key = i.next();
240
241                 // a little aggressive with the repositoryScanning and databaseScanning - should be no need to split
242                 // that configuration
243                 if ( key.startsWith( "repositories" ) || key.startsWith( "proxyConnectors" )
244                     || key.startsWith( "networkProxies" ) || key.startsWith( "repositoryScanning" )
245                     || key.startsWith( "databaseScanning" ) || key.startsWith( "remoteRepositories" )
246                     || key.startsWith( "managedRepositories" ) )
247                 {
248                     foundList = true;
249                 }
250             }
251
252             if ( foundList )
253             {
254                 this.configuration = null;
255
256                 throw new IndeterminateConfigurationException(
257                                                                "Configuration can not be saved when it is loaded from two sources" );
258             }
259         }
260
261         // escape all cron expressions to handle ','
262         for ( Iterator<ManagedRepositoryConfiguration> i = configuration.getManagedRepositories().iterator(); i
263             .hasNext(); )
264         {
265             ManagedRepositoryConfiguration c = i.next();
266             c.setRefreshCronExpression( escapeCronExpression( c.getRefreshCronExpression() ) );
267         }
268
269         if ( configuration.getDatabaseScanning() != null )
270         {
271             configuration.getDatabaseScanning().setCronExpression(
272                                                                    escapeCronExpression( configuration
273                                                                        .getDatabaseScanning().getCronExpression() ) );
274         }
275
276         new ConfigurationRegistryWriter().write( configuration, section );
277         section.save();
278
279         triggerEvent( ConfigurationEvent.SAVED );
280
281         this.configuration = processExpressions( configuration );
282     }
283
284     private Registry createDefaultConfigurationFile()
285         throws RegistryException
286     {
287         // TODO: may not be needed under commons-configuration 1.4 - check
288         // UPDATE: Upgrading to commons-configuration 1.4 breaks half the unit tests. 2007-10-11 (joakime)
289         
290         String contents = "<configuration />";
291         if ( !writeFile( "user configuration", userConfigFilename, contents ) )
292         {
293             if ( !writeFile( "alternative configuration", altConfigFilename, contents ) )
294             {
295                 throw new RegistryException( "Unable to create configuration file in either user ["
296                     + userConfigFilename + "] or alternative [" + altConfigFilename
297                     + "] locations on disk, usually happens when not allowed to write to those locations." );
298             }
299         }
300
301         try
302         {
303             ( (Initializable) registry ).initialize();
304
305             for ( RegistryListener regListener : registryListeners )
306             {
307                 addRegistryChangeListener( regListener );
308             }
309         }
310         catch ( InitializationException e )
311         {
312             throw new RegistryException( "Unable to reinitialize configuration: " + e.getMessage(), e );
313         }
314
315         triggerEvent( ConfigurationEvent.SAVED );
316
317         return registry.getSection( KEY + ".user" );
318     }
319
320     /**
321      * Attempts to write the contents to a file, if an IOException occurs, return false.
322      * 
323      * @param filetype the filetype (freeform text) to use in logging messages when failure to write.
324      * @param path the path to write to.
325      * @param contents the contents to write.
326      * @return true if write successful.
327      */
328     private boolean writeFile( String filetype, String path, String contents )
329     {
330         File file = new File( path );
331
332         try
333         {
334             FileUtils.writeStringToFile( file, contents, "UTF-8" );
335             return true;
336         }
337         catch ( IOException e )
338         {
339             getLogger().error( "Unable to create " + filetype + " file: " + e.getMessage(), e );
340             return false;
341         }
342     }
343
344     private void triggerEvent( int type )
345     {
346         ConfigurationEvent evt = new ConfigurationEvent( type );
347         for ( ConfigurationListener listener : listeners )
348         {
349             try
350             {
351                 listener.configurationEvent( evt );
352             }
353             catch ( Throwable t )
354             {
355                 getLogger().warn( "Unable to notify of saved configuration event.", t );
356             }
357         }
358     }
359
360     public void addListener( ConfigurationListener listener )
361     {
362         if ( listener == null )
363         {
364             return;
365         }
366
367         listeners.add( listener );
368     }
369
370     public void removeListener( ConfigurationListener listener )
371     {
372         if ( listener == null )
373         {
374             return;
375         }
376
377         listeners.remove( listener );
378     }
379
380     public void addChangeListener( RegistryListener listener )
381     {
382         addRegistryChangeListener( listener );
383
384         // keep track for later
385         registryListeners.add( listener );
386     }
387
388     private void addRegistryChangeListener( RegistryListener listener )
389     {
390         Registry section = registry.getSection( KEY + ".user" );
391         if ( section != null )
392         {
393             section.addChangeListener( listener );
394         }
395         section = registry.getSection( KEY + ".base" );
396         if ( section != null )
397         {
398             section.addChangeListener( listener );
399         }
400     }
401
402     public void initialize()
403         throws InitializationException
404     {
405         // Resolve expressions in the userConfigFilename and altConfigFilename
406         try
407         {
408             ExpressionEvaluator expressionEvaluator = new DefaultExpressionEvaluator();
409             expressionEvaluator.addExpressionSource( new SystemPropertyExpressionSource() );
410             userConfigFilename = expressionEvaluator.expand( userConfigFilename );
411             altConfigFilename = expressionEvaluator.expand( altConfigFilename );
412         }
413         catch ( EvaluatorException e )
414         {
415             throw new InitializationException( "Unable to evaluate expressions found in "
416                 + "userConfigFilename or altConfigFilename." );
417         }
418
419         registry.addChangeListener( this );
420     }
421
422     public void beforeConfigurationChange( Registry registry, String propertyName, Object propertyValue )
423     {
424         // nothing to do here
425     }
426
427     public synchronized void afterConfigurationChange( Registry registry, String propertyName, Object propertyValue )
428     {
429         configuration = null;
430     }
431
432     private String removeExpressions( String directory )
433     {
434         String value = StringUtils.replace( directory, "${appserver.base}", registry.getString( "appserver.base",
435                                                                                                 "${appserver.base}" ) );
436         value = StringUtils.replace( value, "${appserver.home}", registry.getString( "appserver.home",
437                                                                                      "${appserver.home}" ) );
438         return value;
439     }
440
441     private String unescapeCronExpression( String cronExpression )
442     {
443         return StringUtils.replace( cronExpression, "\\,", "," );
444     }
445
446     private String escapeCronExpression( String cronExpression )
447     {
448         return StringUtils.replace( cronExpression, ",", "\\," );
449     }
450
451     private Configuration processExpressions( Configuration config )
452     {
453         // TODO: for commons-configuration 1.3 only
454         for ( Iterator<ManagedRepositoryConfiguration> i = config.getManagedRepositories().iterator(); i.hasNext(); )
455         {
456             ManagedRepositoryConfiguration c = i.next();
457             c.setLocation( removeExpressions( c.getLocation() ) );
458             c.setRefreshCronExpression( unescapeCronExpression( c.getRefreshCronExpression() ) );
459         }
460
461         DatabaseScanningConfiguration databaseScanning = config.getDatabaseScanning();
462         if ( databaseScanning != null )
463         {
464             String cron = databaseScanning.getCronExpression();
465             databaseScanning.setCronExpression( unescapeCronExpression( cron ) );
466         }
467
468         return config;
469     }
470
471     public String getUserConfigFilename()
472     {
473         return userConfigFilename;
474     }
475
476     public String getAltConfigFilename()
477     {
478         return altConfigFilename;
479     }
480
481     public boolean isDefaulted()
482     {
483         return this.isConfigurationDefaulted;
484     }
485 }