View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.shared.release.phase;
20  
21  import java.util.Arrays;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.Properties;
25  import java.util.concurrent.atomic.AtomicReference;
26  
27  import org.apache.maven.artifact.ArtifactUtils;
28  import org.apache.maven.project.MavenProject;
29  import org.apache.maven.scm.manager.NoSuchScmProviderException;
30  import org.apache.maven.scm.provider.ScmProvider;
31  import org.apache.maven.scm.repository.ScmRepository;
32  import org.apache.maven.scm.repository.ScmRepositoryException;
33  import org.apache.maven.shared.release.ReleaseExecutionException;
34  import org.apache.maven.shared.release.ReleaseResult;
35  import org.apache.maven.shared.release.config.ReleaseDescriptor;
36  import org.apache.maven.shared.release.env.ReleaseEnvironment;
37  import org.apache.maven.shared.release.policy.PolicyException;
38  import org.apache.maven.shared.release.policy.naming.NamingPolicy;
39  import org.apache.maven.shared.release.policy.naming.NamingPolicyRequest;
40  import org.apache.maven.shared.release.scm.ReleaseScmRepositoryException;
41  import org.apache.maven.shared.release.scm.ScmRepositoryConfigurator;
42  import org.apache.maven.shared.release.util.ReleaseUtil;
43  import org.codehaus.plexus.components.interactivity.Prompter;
44  import org.codehaus.plexus.components.interactivity.PrompterException;
45  import org.codehaus.plexus.interpolation.InterpolationException;
46  import org.codehaus.plexus.interpolation.Interpolator;
47  import org.codehaus.plexus.interpolation.PrefixAwareRecursionInterceptor;
48  import org.codehaus.plexus.interpolation.PrefixedPropertiesValueSource;
49  import org.codehaus.plexus.interpolation.RecursionInterceptor;
50  import org.codehaus.plexus.interpolation.StringSearchInterpolator;
51  
52  import static java.util.Objects.requireNonNull;
53  import static org.apache.maven.shared.utils.logging.MessageUtils.buffer;
54  
55  /**
56   * Input any variables that were not yet configured.
57   *
58   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
59   */
60  public abstract class AbstractInputVariablesPhase extends AbstractReleasePhase {
61      /**
62       * Component used to prompt for input.
63       */
64      private final AtomicReference<Prompter> prompter;
65  
66      /**
67       * Tool that gets a configured SCM repository from release configuration.
68       */
69      private final ScmRepositoryConfigurator scmRepositoryConfigurator;
70  
71      /**
72       * Component used for custom or default naming policy
73       */
74      private final Map<String, NamingPolicy> namingPolicies;
75  
76      /**
77       * Whether this is a branch or a tag operation.
78       */
79      private final boolean branchOperation;
80  
81      /**
82       * The default naming policy to apply, if any
83       */
84      private final String defaultNamingPolicy;
85  
86      protected AbstractInputVariablesPhase(
87              Prompter prompter,
88              ScmRepositoryConfigurator scmRepositoryConfigurator,
89              Map<String, NamingPolicy> namingPolicies,
90              boolean branchOperation,
91              String defaultNamingPolicy) {
92          this.prompter = new AtomicReference<>(requireNonNull(prompter));
93          this.scmRepositoryConfigurator = requireNonNull(scmRepositoryConfigurator);
94          this.namingPolicies = requireNonNull(namingPolicies);
95          this.branchOperation = branchOperation;
96          this.defaultNamingPolicy = defaultNamingPolicy;
97      }
98  
99      /**
100      * For easier testing only!
101      */
102     public void setPrompter(Prompter prompter) {
103         this.prompter.set(prompter);
104     }
105 
106     boolean isBranchOperation() {
107         return branchOperation;
108     }
109 
110     /**
111      * <p>getScmProvider.</p>
112      *
113      * @param releaseDescriptor  a {@link ReleaseDescriptor} object
114      * @param releaseEnvironment a {@link ReleaseEnvironment} object
115      * @return a {@link ScmProvider} object
116      * @throws ReleaseScmRepositoryException if any.
117      * @throws ReleaseExecutionException     if any.
118      */
119     protected ScmProvider getScmProvider(ReleaseDescriptor releaseDescriptor, ReleaseEnvironment releaseEnvironment)
120             throws ReleaseScmRepositoryException, ReleaseExecutionException {
121         try {
122             ScmRepository repository = scmRepositoryConfigurator.getConfiguredRepository(
123                     releaseDescriptor, releaseEnvironment.getSettings());
124 
125             return scmRepositoryConfigurator.getRepositoryProvider(repository);
126         } catch (ScmRepositoryException e) {
127             throw new ReleaseScmRepositoryException(
128                     e.getMessage() + " for URL: " + releaseDescriptor.getScmSourceUrl(), e.getValidationMessages());
129         } catch (NoSuchScmProviderException e) {
130             throw new ReleaseExecutionException("Unable to configure SCM repository: " + e.getMessage(), e);
131         }
132     }
133 
134     @Override
135     public ReleaseResult execute(
136             ReleaseDescriptor releaseDescriptor,
137             ReleaseEnvironment releaseEnvironment,
138             List<MavenProject> reactorProjects)
139             throws ReleaseExecutionException {
140         ReleaseResult result = new ReleaseResult();
141 
142         // get the root project
143         MavenProject project = ReleaseUtil.getRootProject(reactorProjects);
144 
145         String tag = releaseDescriptor.getScmReleaseLabel();
146 
147         if (tag == null) {
148             // Must get default version from mapped versions, as the project will be the incorrect snapshot
149             String key = ArtifactUtils.versionlessKey(project.getGroupId(), project.getArtifactId());
150             String releaseVersion = releaseDescriptor.getProjectReleaseVersion(key);
151             if (releaseVersion == null) {
152                 throw new ReleaseExecutionException("Project tag cannot be selected if version is not yet mapped");
153             }
154 
155             String suggestedName;
156             String scmTagNameFormat = releaseDescriptor.getScmTagNameFormat();
157             if (releaseDescriptor.getProjectNamingPolicyId() != null) {
158                 try {
159                     suggestedName =
160                             resolveSuggestedName(releaseDescriptor.getProjectNamingPolicyId(), releaseVersion, project);
161                 } catch (PolicyException e) {
162                     throw new ReleaseExecutionException(e.getMessage(), e);
163                 }
164             } else if (scmTagNameFormat != null) {
165                 Interpolator interpolator = new StringSearchInterpolator("@{", "}");
166                 List<String> possiblePrefixes = Arrays.asList("project", "pom");
167                 Properties values = new Properties();
168                 values.setProperty("artifactId", project.getArtifactId());
169                 values.setProperty("groupId", project.getGroupId());
170                 values.setProperty("version", releaseVersion);
171                 interpolator.addValueSource(new PrefixedPropertiesValueSource(possiblePrefixes, values, true));
172                 RecursionInterceptor recursionInterceptor = new PrefixAwareRecursionInterceptor(possiblePrefixes);
173                 try {
174                     suggestedName = interpolator.interpolate(scmTagNameFormat, recursionInterceptor);
175                 } catch (InterpolationException e) {
176                     throw new ReleaseExecutionException(
177                             "Could not interpolate specified tag name format: " + scmTagNameFormat, e);
178                 }
179             } else {
180                 try {
181                     suggestedName = resolveSuggestedName(defaultNamingPolicy, releaseVersion, project);
182                 } catch (PolicyException e) {
183                     throw new ReleaseExecutionException(e.getMessage(), e);
184                 }
185             }
186 
187             ScmProvider provider;
188             try {
189                 provider = getScmProvider(releaseDescriptor, releaseEnvironment);
190             } catch (ReleaseScmRepositoryException e) {
191                 throw new ReleaseExecutionException(
192                         "No scm provider can be found for url: " + releaseDescriptor.getScmSourceUrl(), e);
193             }
194 
195             suggestedName = provider.sanitizeTagName(suggestedName);
196 
197             if (releaseDescriptor.isInteractive()) {
198                 try {
199                     if (branchOperation) {
200                         tag = prompter.get()
201                                 .prompt("What is the branch name for \"" + project.getName() + "\"? ("
202                                         + buffer().project(project.getArtifactId()) + ")");
203                         if (tag == null || tag.isEmpty()) {
204                             throw new ReleaseExecutionException("No branch name was given.");
205                         }
206                     } else {
207                         tag = prompter.get()
208                                 .prompt(
209                                         "What is the SCM release tag or label for \"" + project.getName() + "\"? ("
210                                                 + buffer().project(project.getArtifactId()) + ")",
211                                         suggestedName);
212                     }
213                 } catch (PrompterException e) {
214                     throw new ReleaseExecutionException(
215                             "Error reading version from input handler: " + e.getMessage(), e);
216                 }
217             } else if (suggestedName == null) {
218                 if (isBranchOperation()) {
219                     throw new ReleaseExecutionException("No branch name was given.");
220                 } else {
221                     throw new ReleaseExecutionException("No tag name was given.");
222                 }
223             } else {
224                 tag = suggestedName;
225             }
226             releaseDescriptor.setScmReleaseLabel(tag);
227         }
228 
229         result.setResultCode(ReleaseResult.SUCCESS);
230 
231         return result;
232     }
233 
234     @Override
235     public ReleaseResult simulate(
236             ReleaseDescriptor releaseDescriptor,
237             ReleaseEnvironment releaseEnvironment,
238             List<MavenProject> reactorProjects)
239             throws ReleaseExecutionException {
240         ReleaseResult result = new ReleaseResult();
241 
242         // It makes no modifications, so simulate is the same as execute
243         execute(releaseDescriptor, releaseEnvironment, reactorProjects);
244 
245         result.setResultCode(ReleaseResult.SUCCESS);
246 
247         return result;
248     }
249 
250     private String resolveSuggestedName(String policyId, String version, MavenProject project) throws PolicyException {
251         if (policyId == null) {
252             return null;
253         }
254 
255         NamingPolicy policy = namingPolicies.get(policyId);
256         if (policy == null) {
257             throw new PolicyException("Policy '" + policyId + "' is unknown, available: " + namingPolicies.keySet());
258         }
259 
260         NamingPolicyRequest request = new NamingPolicyRequest()
261                 .setGroupId(project.getGroupId())
262                 .setArtifactId(project.getArtifactId())
263                 .setVersion(version);
264         return policy.getName(request).getName();
265     }
266 }