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