View Javadoc
1   package org.apache.maven.shared.release.config;
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 java.io.File;
23  import java.io.FileInputStream;
24  import java.io.FileNotFoundException;
25  import java.io.FileOutputStream;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.io.OutputStream;
29  import java.util.Map;
30  import java.util.Properties;
31  
32  import org.apache.maven.model.Scm;
33  import org.apache.maven.shared.release.config.ReleaseDescriptorBuilder.BuilderReleaseDescriptor;
34  import org.apache.maven.shared.release.scm.IdentifiedScm;
35  import org.codehaus.plexus.component.annotations.Component;
36  import org.codehaus.plexus.component.annotations.Requirement;
37  import org.codehaus.plexus.logging.AbstractLogEnabled;
38  import org.codehaus.plexus.util.StringUtils;
39  import org.sonatype.plexus.components.cipher.DefaultPlexusCipher;
40  import org.sonatype.plexus.components.cipher.PlexusCipherException;
41  import org.sonatype.plexus.components.sec.dispatcher.DefaultSecDispatcher;
42  import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher;
43  import org.sonatype.plexus.components.sec.dispatcher.SecDispatcherException;
44  import org.sonatype.plexus.components.sec.dispatcher.SecUtil;
45  import org.sonatype.plexus.components.sec.dispatcher.model.SettingsSecurity;
46  
47  /**
48   * Read and write release configuration and state from a properties file.
49   *
50   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
51   */
52  @Component( role = ReleaseDescriptorStore.class, hint = "properties" )
53  public class PropertiesReleaseDescriptorStore
54      extends AbstractLogEnabled
55      implements ReleaseDescriptorStore
56  {
57  
58      /**
59       * When this plugin requires Maven 3.0 as minimum, this component can be removed and o.a.m.s.c.SettingsDecrypter be
60       * used instead.
61       */
62      @Requirement( role = SecDispatcher.class, hint = "mng-4384" )
63      private DefaultSecDispatcher secDispatcher;
64  
65      @Override
66      public ReleaseDescriptorBuilder read( ReleaseDescriptorBuilder mergeDescriptor )
67          throws ReleaseDescriptorStoreException
68      {
69          return read( mergeDescriptor, getDefaultReleasePropertiesFile( mergeDescriptor.build() ) );
70      }
71  
72      /**
73       * <p>read.</p>
74       *
75       * @param file a {@link java.io.File} object
76       * @return a {@link org.apache.maven.shared.release.config.ReleaseDescriptorBuilder} object
77       * @throws org.apache.maven.shared.release.config.ReleaseDescriptorStoreException if any.
78       */
79      public ReleaseDescriptorBuilder read( File file )
80          throws ReleaseDescriptorStoreException
81      {
82          return read( null, file );
83      }
84  
85      /**
86       * <p>read.</p>
87       *
88       * @param mergeDescriptor a {@link org.apache.maven.shared.release.config.ReleaseDescriptorBuilder} object
89       * @param file a {@link java.io.File} object
90       * @return a {@link org.apache.maven.shared.release.config.ReleaseDescriptorBuilder} object
91       * @throws org.apache.maven.shared.release.config.ReleaseDescriptorStoreException if any.
92       */
93      public ReleaseDescriptorBuilder read( ReleaseDescriptorBuilder mergeDescriptor, File file )
94          throws ReleaseDescriptorStoreException
95      {
96          Properties properties = new Properties();
97  
98          try ( InputStream inStream = new FileInputStream( file ) )
99          {
100             properties.load( inStream );
101         }
102         catch ( FileNotFoundException e )
103         {
104             getLogger().debug( file.getName() + " not found - using empty properties" );
105         }
106         catch ( IOException e )
107         {
108             throw new ReleaseDescriptorStoreException(
109                 "Error reading properties file '" + file.getName() + "': " + e.getMessage(), e );
110         }
111         
112         try
113         {
114             decryptProperties( properties );
115         }
116         catch ( IllegalStateException | SecDispatcherException | PlexusCipherException e )
117         {
118             getLogger().debug( e.getMessage() );
119         }
120 
121         ReleaseDescriptorBuilder builder;
122         if ( mergeDescriptor != null )
123         {
124             builder = mergeDescriptor;
125         }
126         else
127         {
128            builder = new ReleaseDescriptorBuilder(); 
129         }
130         
131         ReleaseUtils.copyPropertiesToReleaseDescriptor( properties, builder );
132 
133         return builder;
134     }
135 
136     @Override
137     public void write( ReleaseDescriptor config )
138         throws ReleaseDescriptorStoreException
139     {
140         write( (BuilderReleaseDescriptor) config, getDefaultReleasePropertiesFile( config ) );
141     }
142 
143     @Override
144     public void delete( ReleaseDescriptor config )
145     {
146         File file = getDefaultReleasePropertiesFile( config );
147         if ( file.exists() )
148         {
149             file.delete();
150         }
151     }
152 
153     /**
154      * <p>write.</p>
155      *
156      * @param config a {@link org.apache.maven.shared.release.config.ReleaseDescriptorBuilder.BuilderReleaseDescriptor}
157      *               object
158      * @param file a {@link java.io.File} object
159      * @throws org.apache.maven.shared.release.config.ReleaseDescriptorStoreException if any.
160      */
161     public void write( BuilderReleaseDescriptor config, File file )
162         throws ReleaseDescriptorStoreException
163     {
164         Properties properties = new Properties();
165         properties.setProperty( "completedPhase", config.getCompletedPhase() );
166         if ( config.isCommitByProject() ) //default is false
167         {
168             properties.setProperty( "commitByProject", "true" );
169         }
170         properties.setProperty( "scm.url", config.getScmSourceUrl() );
171         if ( config.getScmId() != null )
172         {
173             properties.setProperty( "scm.id", config.getScmId() );
174         }
175         if ( config.getScmUsername() != null )
176         {
177             properties.setProperty( "scm.username", config.getScmUsername() );
178         }
179         if ( config.getScmPassword() != null )
180         {
181             String password = config.getScmPassword();
182             try
183             {
184                 password = encryptAndDecorate( password );
185             }
186             catch ( IllegalStateException | SecDispatcherException | PlexusCipherException e )
187             {
188                 getLogger().debug( e.getMessage() );
189             }
190             properties.setProperty( "scm.password", password );
191         }
192         if ( config.getScmPrivateKey() != null )
193         {
194             properties.setProperty( "scm.privateKey", config.getScmPrivateKey() );
195         }
196         if ( config.getScmPrivateKeyPassPhrase() != null )
197         {
198             String passPhrase = config.getScmPrivateKeyPassPhrase();
199             try
200             {
201                 passPhrase = encryptAndDecorate( passPhrase );
202             }
203             catch ( IllegalStateException | SecDispatcherException | PlexusCipherException e )
204             {
205                 getLogger().debug( e.getMessage() );
206             }
207             properties.setProperty( "scm.passphrase", passPhrase  );
208         }
209         if ( config.getScmTagBase() != null )
210         {
211             properties.setProperty( "scm.tagBase", config.getScmTagBase() );
212         }
213         if ( config.getScmBranchBase() != null )
214         {
215             properties.setProperty( "scm.branchBase", config.getScmBranchBase() );
216         }
217         if ( config.getScmReleaseLabel() != null )
218         {
219             properties.setProperty( "scm.tag", config.getScmReleaseLabel() );
220         }
221         if ( config.getScmTagNameFormat() != null )
222         {
223             properties.setProperty( "scm.tagNameFormat", config.getScmTagNameFormat() );
224         }
225         if ( config.getScmCommentPrefix() != null )
226         {
227             properties.setProperty( "scm.commentPrefix", config.getScmCommentPrefix() );
228         }
229         if ( config.getScmDevelopmentCommitComment() != null )
230         {
231             properties.setProperty( "scm.developmentCommitComment", config.getScmDevelopmentCommitComment() );
232         }
233         if ( config.getScmReleaseCommitComment() != null )
234         {
235             properties.setProperty( "scm.releaseCommitComment", config.getScmReleaseCommitComment() );
236         }
237         if ( config.getScmBranchCommitComment() != null )
238         {
239             properties.setProperty( "scm.branchCommitComment", config.getScmBranchCommitComment() );
240         }
241         if ( config.getScmRollbackCommitComment() != null )
242         {
243             properties.setProperty( "scm.rollbackCommitComment", config.getScmRollbackCommitComment() );
244         }
245         if ( config.getAdditionalArguments() != null )
246         {
247             properties.setProperty( "exec.additionalArguments", config.getAdditionalArguments() );
248         }
249         if ( config.getPomFileName() != null )
250         {
251             properties.setProperty( "exec.pomFileName", config.getPomFileName() );
252         }
253         if ( !config.getActivateProfiles().isEmpty() )
254         {
255             properties.setProperty( "exec.activateProfiles",
256                                     StringUtils.join( config.getActivateProfiles().iterator(), "," ) );
257         }
258         if ( config.getPreparationGoals() != null )
259         {
260             properties.setProperty( "preparationGoals", config.getPreparationGoals() );
261         }
262         if ( config.getCompletionGoals() != null )
263         {
264             properties.setProperty( "completionGoals", config.getCompletionGoals() );
265         }
266         if ( config.getProjectVersionPolicyId() != null )
267         {
268             properties.setProperty( "projectVersionPolicyId", config.getProjectVersionPolicyId() );
269         }
270         if ( config.getProjectNamingPolicyId() != null )
271         {
272             properties.setProperty( "projectNamingPolicyId", config.getProjectNamingPolicyId() );
273         }
274         if ( config.getReleaseStrategyId() != null )
275         {
276             properties.setProperty( "releaseStrategyId", config.getReleaseStrategyId() );
277         }
278 
279         properties.setProperty( "exec.snapshotReleasePluginAllowed",
280                                 Boolean.toString( config.isSnapshotReleasePluginAllowed() ) );
281 
282         properties.setProperty( "remoteTagging", Boolean.toString( config.isRemoteTagging() ) );
283 
284         properties.setProperty( "pinExternals", Boolean.toString( config.isPinExternals() ) );
285 
286         properties.setProperty( "pushChanges", Boolean.toString( config.isPushChanges() ) );
287 
288         if ( config.getWorkItem() != null )
289         {
290             properties.setProperty( "workItem", config.getWorkItem() );
291         }
292 
293         if ( config.getAutoResolveSnapshots() != null )
294         {
295             properties.setProperty( "autoResolveSnapshots", config.getAutoResolveSnapshots() );
296         }
297 
298         // others boolean properties are not written to the properties file because the value from the caller is always
299         // used
300 
301         
302         for ( Map.Entry<String, ReleaseStageVersions> entry : config.getProjectVersions().entrySet() )
303         {
304             if ( entry.getValue().getRelease() != null )
305             {
306                 properties.setProperty( "project.rel." + entry.getKey(), entry.getValue().getRelease() );
307             }
308             if ( entry.getValue().getDevelopment() != null )
309             {
310                 properties.setProperty( "project.dev." + entry.getKey(), entry.getValue().getDevelopment() );
311             }
312         }
313 
314         for ( Map.Entry<String, Scm> entry : config.getOriginalScmInfo().entrySet() )
315         {
316             Scm scm = entry.getValue();
317             String prefix = "project.scm." + entry.getKey();
318             if ( scm != null )
319             {
320                 if ( scm.getConnection() != null )
321                 {
322                     properties.setProperty( prefix + ".connection", scm.getConnection() );
323                 }
324                 if ( scm.getDeveloperConnection() != null )
325                 {
326                     properties.setProperty( prefix + ".developerConnection", scm.getDeveloperConnection() );
327                 }
328                 if ( scm.getUrl() != null )
329                 {
330                     properties.setProperty( prefix + ".url", scm.getUrl() );
331                 }
332                 if ( scm.getTag() != null )
333                 {
334                     properties.setProperty( prefix + ".tag", scm.getTag() );
335                 }
336                 if ( scm instanceof IdentifiedScm )
337                 {
338                     IdentifiedScm identifiedScm = (IdentifiedScm) scm;
339                     if ( identifiedScm.getId() != null )
340                     {
341                         properties.setProperty( prefix + ".id", identifiedScm.getId() );
342                     }
343                 }
344             }
345             else
346             {
347                 properties.setProperty( prefix + ".empty", "true" );
348             }
349         }
350 
351         if ( ( config.getResolvedSnapshotDependencies() != null )
352             && ( config.getResolvedSnapshotDependencies().size() > 0 ) )
353         {
354             processResolvedDependencies( properties, config.getResolvedSnapshotDependencies() );
355         }
356 
357         try ( OutputStream outStream = new FileOutputStream( file ) )
358         {
359             properties.store( outStream, "release configuration" );
360         }
361         catch ( IOException e )
362         {
363             throw new ReleaseDescriptorStoreException(
364                 "Error writing properties file '" + file.getName() + "': " + e.getMessage(), e );
365         }
366     }
367 
368     private void processResolvedDependencies( Properties prop, Map<String, ReleaseStageVersions> resolvedDependencies )
369     {
370         for ( Map.Entry<String, ReleaseStageVersions> currentEntry : resolvedDependencies.entrySet() )
371         {
372             ReleaseStageVersions versionMap = currentEntry.getValue();
373             
374             prop.setProperty( "dependency." + currentEntry.getKey() + ".release",
375                               versionMap.getRelease() );
376             prop.setProperty( "dependency." + currentEntry.getKey() + ".development",
377                               versionMap.getDevelopment() );
378         }
379     }
380 
381     private static File getDefaultReleasePropertiesFile( ReleaseDescriptor mergeDescriptor )
382     {
383         return new File( mergeDescriptor.getWorkingDirectory(), "release.properties" );
384     }
385     
386     private void decryptProperties( Properties properties )
387         throws IllegalStateException, SecDispatcherException, PlexusCipherException
388     {
389         String[] keys = new String[] { "scm.password", "scm.passphrase" };
390 
391         for ( String key : keys )
392         {
393             String value = properties.getProperty( key );
394             if ( value != null )
395             {
396                 properties.put( key, decrypt( value ) );
397             }
398         }
399     }
400 
401     // From org.apache.maven.cli.MavenCli.encryption(CliRequest)
402     private String encryptAndDecorate( String passwd )
403         throws IllegalStateException, SecDispatcherException, PlexusCipherException
404     {
405         final String master = getMaster();
406 
407         DefaultPlexusCipher cipher = new DefaultPlexusCipher();
408         String masterPasswd = cipher.decryptDecorated( master, DefaultSecDispatcher.SYSTEM_PROPERTY_SEC_LOCATION );
409         return cipher.encryptAndDecorate( passwd, masterPasswd );
410     }
411     
412     private String decrypt( String value ) throws IllegalStateException, SecDispatcherException, PlexusCipherException
413     {
414         final String master = getMaster();
415 
416         DefaultPlexusCipher cipher = new DefaultPlexusCipher();
417         String masterPasswd = cipher.decryptDecorated( master, DefaultSecDispatcher.SYSTEM_PROPERTY_SEC_LOCATION );
418         return cipher.decryptDecorated( value, masterPasswd );
419     }
420     
421     private String getMaster() throws SecDispatcherException 
422     {
423         String configurationFile = secDispatcher.getConfigurationFile();
424 
425         if ( configurationFile.startsWith( "~" ) )
426         {
427             configurationFile = System.getProperty( "user.home" ) + configurationFile.substring( 1 );
428         }
429 
430         String file = System.getProperty( DefaultSecDispatcher.SYSTEM_PROPERTY_SEC_LOCATION, configurationFile );
431 
432         String master = null;
433 
434         SettingsSecurity sec = SecUtil.read( file, true );
435         if ( sec != null )
436         {
437             master = sec.getMaster();
438         }
439 
440         if ( master == null )
441         {
442             throw new IllegalStateException( "Master password is not set in the setting security file: " + file );
443         }
444         
445         return master;
446     }
447 
448 }