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