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