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