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.exception;
20  
21  import javax.inject.Named;
22  import javax.inject.Singleton;
23  
24  import java.io.IOException;
25  import java.net.ConnectException;
26  import java.net.UnknownHostException;
27  import java.util.ArrayList;
28  import java.util.Collections;
29  import java.util.IdentityHashMap;
30  import java.util.List;
31  import java.util.Set;
32  
33  import org.apache.maven.api.plugin.MojoException;
34  import org.apache.maven.lifecycle.LifecycleExecutionException;
35  import org.apache.maven.model.building.ModelProblem;
36  import org.apache.maven.model.building.ModelProblemUtils;
37  import org.apache.maven.plugin.AbstractMojoExecutionException;
38  import org.apache.maven.plugin.MojoExecutionException;
39  import org.apache.maven.plugin.PluginContainerException;
40  import org.apache.maven.plugin.PluginExecutionException;
41  import org.apache.maven.project.ProjectBuildingException;
42  import org.apache.maven.project.ProjectBuildingResult;
43  import org.eclipse.aether.transfer.ArtifactFilteredOutException;
44  
45  /*
46  
47  - test projects for each of these
48  - how to categorize the problems so that the id of the problem can be match to a page with descriptive help and the test
49    project
50  - nice little sample projects that could be run in the core as well as integration tests
51  
52  All Possible Errors
53  - invalid lifecycle phase (maybe same as bad CLI param, though you were talking about embedder too)
54  - <module> specified is not found
55  - malformed settings
56  - malformed POM
57  - local repository not writable
58  - remote repositories not available
59  - artifact metadata missing
60  - extension metadata missing
61  - extension artifact missing
62  - artifact metadata retrieval problem
63  - version range violation
64  - circular dependency
65  - artifact missing
66  - artifact retrieval exception
67  - md5 checksum doesn't match for local artifact, need to redownload this
68  - POM doesn't exist for a goal that requires one
69  - parent POM missing (in both the repository + relative path)
70  - component not found
71  
72  Plugins:
73  - plugin metadata missing
74  - plugin metadata retrieval problem
75  - plugin artifact missing
76  - plugin artifact retrieval problem
77  - plugin dependency metadata missing
78  - plugin dependency metadata retrieval problem
79  - plugin configuration problem
80  - plugin execution failure due to something that is known to possibly go wrong (like compilation failure)
81  - plugin execution error due to something that is not expected to go wrong (the compiler executable missing)
82  - asking to use a plugin for which you do not have a version defined - tools to easily select versions
83  - goal not found in a plugin (probably could list the ones that are)
84  
85   */
86  
87  // PluginNotFoundException, PluginResolutionException, PluginDescriptorParsingException,
88  // CycleDetectedInPluginGraphException;
89  
90  /**
91   * Transform an exception into useful end-user message.
92   */
93  @Named
94  @Singleton
95  public class DefaultExceptionHandler implements ExceptionHandler {
96      @Override
97      public ExceptionSummary handleException(Throwable exception) {
98          return handle("", exception);
99      }
100 
101     private ExceptionSummary handle(String message, Throwable exception) {
102         String reference = getReference(Collections.newSetFromMap(new IdentityHashMap<>()), exception);
103 
104         List<ExceptionSummary> children = null;
105 
106         if (exception instanceof ProjectBuildingException projectBuildingException) {
107             List<ProjectBuildingResult> results = projectBuildingException.getResults();
108 
109             children = new ArrayList<>();
110 
111             for (ProjectBuildingResult result : results) {
112                 ExceptionSummary child = handle(result);
113                 if (child != null) {
114                     children.add(child);
115                 }
116             }
117 
118             message = "The build could not read " + children.size() + " project" + (children.size() == 1 ? "" : "s");
119         } else {
120             message = getMessage(message, exception);
121         }
122 
123         return new ExceptionSummary(exception, message, reference, children);
124     }
125 
126     private ExceptionSummary handle(ProjectBuildingResult result) {
127         List<ExceptionSummary> children = new ArrayList<>();
128 
129         for (ModelProblem problem : result.getProblems()) {
130             ExceptionSummary child = handle(problem, result.getProjectId());
131             if (child != null) {
132                 children.add(child);
133             }
134         }
135 
136         if (children.isEmpty()) {
137             return null;
138         }
139 
140         String message = System.lineSeparator()
141                 + "The project " + (result.getProjectId().isEmpty() ? "" : result.getProjectId() + " ")
142                 + "(" + result.getPomFile() + ") has "
143                 + children.size() + " error" + (children.size() == 1 ? "" : "s");
144 
145         return new ExceptionSummary(null, message, null, children);
146     }
147 
148     private ExceptionSummary handle(ModelProblem problem, String projectId) {
149         if (ModelProblem.Severity.ERROR.compareTo(problem.getSeverity()) >= 0) {
150             String message = problem.getMessage();
151 
152             String location = ModelProblemUtils.formatLocation(problem, projectId);
153 
154             if (!location.isEmpty()) {
155                 message += " @ " + location;
156             }
157 
158             return handle(message, problem.getException());
159         } else {
160             return null;
161         }
162     }
163 
164     private String getReference(Set<Throwable> dejaVu, Throwable exception) {
165         String reference = "";
166         if (!dejaVu.add(exception)) {
167             return reference;
168         }
169 
170         if (exception != null) {
171             if (exception instanceof MojoExecutionException) {
172                 reference = MojoExecutionException.class.getSimpleName();
173 
174                 Throwable cause = exception.getCause();
175                 if (cause instanceof IOException) {
176                     cause = cause.getCause();
177                     if (cause instanceof ConnectException) {
178                         reference = ConnectException.class.getSimpleName();
179                     }
180                 }
181                 if (findCause(exception, ArtifactFilteredOutException.class) != null) {
182                     reference = "https://maven.apache.org/resolver/remote-repository-filtering.html";
183                 }
184             } else if (exception instanceof LinkageError) {
185                 reference = LinkageError.class.getSimpleName();
186             } else if (exception instanceof PluginExecutionException) {
187                 Throwable cause = exception.getCause();
188 
189                 if (cause instanceof PluginContainerException) {
190                     Throwable cause2 = cause.getCause();
191 
192                     if (cause2 instanceof NoClassDefFoundError) {
193                         String message = cause2.getMessage();
194                         if (message != null && message.contains("org/sonatype/aether/")) {
195                             reference = "AetherClassNotFound";
196                         }
197                     }
198                 }
199 
200                 if (reference == null || reference.isEmpty()) {
201                     reference = getReference(dejaVu, cause);
202                 }
203 
204                 if (reference == null || reference.isEmpty()) {
205                     reference = exception.getClass().getSimpleName();
206                 }
207             } else if (exception instanceof LifecycleExecutionException) {
208                 reference = getReference(dejaVu, exception.getCause());
209             } else if (isNoteworthyException(exception)) {
210                 reference = exception.getClass().getSimpleName();
211             }
212         }
213 
214         if ((reference != null && !reference.isEmpty())
215                 && !reference.startsWith("http:")
216                 && !reference.startsWith("https:")) {
217             reference = "http://cwiki.apache.org/confluence/display/MAVEN/" + reference;
218         }
219 
220         return reference;
221     }
222 
223     private boolean isNoteworthyException(Throwable exception) {
224         if (exception == null) {
225             return false;
226         } else if (exception instanceof Error) {
227             return true;
228         } else if (exception instanceof RuntimeException) {
229             return false;
230         } else {
231             return !exception.getClass().getName().startsWith("java");
232         }
233     }
234 
235     private String getMessage(String message, Throwable exception) {
236         String fullMessage = (message != null) ? message : "";
237 
238         boolean hasArtifactFilteredOut = false;
239 
240         // To break out of possible endless loop when getCause returns "this", or dejaVu for n-level recursion (n>1)
241         Set<Throwable> dejaVu = Collections.newSetFromMap(new IdentityHashMap<>());
242         for (Throwable t = exception; t != null && t != t.getCause(); t = t.getCause()) {
243             String exceptionMessage = t.getMessage();
244             String longMessage = null;
245 
246             if (t instanceof AbstractMojoExecutionException abstractMojoExecutionException) {
247                 longMessage = abstractMojoExecutionException.getLongMessage();
248             } else if (t instanceof MojoException mojoException) {
249                 longMessage = mojoException.getLongMessage();
250             }
251 
252             if (longMessage != null && !longMessage.isEmpty()) {
253                 if ((exceptionMessage == null || exceptionMessage.isEmpty())
254                         || longMessage.contains(exceptionMessage)) {
255                     exceptionMessage = longMessage;
256                 } else if (!exceptionMessage.contains(longMessage)) {
257                     exceptionMessage = join(exceptionMessage, System.lineSeparator() + longMessage);
258                 }
259             }
260 
261             if (exceptionMessage == null || exceptionMessage.isEmpty()) {
262                 exceptionMessage = t.getClass().getSimpleName();
263             }
264 
265             if (t instanceof UnknownHostException && !fullMessage.contains("host")) {
266                 fullMessage = join(fullMessage, "Unknown host " + exceptionMessage);
267             } else if (!fullMessage.contains(exceptionMessage)) {
268                 fullMessage = join(fullMessage, exceptionMessage);
269             }
270 
271             if (t instanceof ArtifactFilteredOutException) {
272                 hasArtifactFilteredOut = true;
273             }
274 
275             if (!dejaVu.add(t)) {
276                 fullMessage = join(fullMessage, "[CIRCULAR REFERENCE]");
277                 break;
278             }
279         }
280 
281         if (hasArtifactFilteredOut) {
282             fullMessage += """
283 
284                 This error indicates that a remote repository filter has rejected this artifact. This commonly happens with repository managers using virtual/group repositories that do not properly aggregate prefix files.
285                 To disable remote repository filtering, add one or both of these to your command line or to .mvn/maven.config:
286                   -Daether.remoteRepositoryFilter.prefixes=false
287                   -Daether.remoteRepositoryFilter.groupId=false
288                 See https://maven.apache.org/resolver/remote-repository-filtering.html""";
289         }
290 
291         return fullMessage.trim();
292     }
293 
294     private static <T extends Throwable> T findCause(Throwable exception, Class<T> type) {
295         Set<Throwable> dejaVu = Collections.newSetFromMap(new IdentityHashMap<>());
296         for (Throwable t = exception; t != null && dejaVu.add(t); t = t.getCause()) {
297             if (type.isInstance(t)) {
298                 return type.cast(t);
299             }
300         }
301         return null;
302     }
303 
304     private String join(String message1, String message2) {
305         String message = "";
306 
307         if (message1 != null && !message1.isEmpty()) {
308             message = message1.trim();
309         }
310 
311         if (message2 != null && !message2.isEmpty()) {
312             if (message != null && !message.isEmpty()) {
313                 if (message.endsWith(".") || message.endsWith("!") || message.endsWith(":")) {
314                     message += " ";
315                 } else {
316                     message += ": ";
317                 }
318             }
319 
320             message += message2;
321         }
322 
323         return message;
324     }
325 }