View Javadoc
1   package org.apache.maven.exception;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.IOException;
23  import java.net.ConnectException;
24  import java.net.UnknownHostException;
25  import java.util.ArrayList;
26  import java.util.List;
27  
28  import org.apache.maven.lifecycle.LifecycleExecutionException;
29  import org.apache.maven.model.building.ModelProblem;
30  import org.apache.maven.model.building.ModelProblemUtils;
31  import org.apache.maven.plugin.AbstractMojoExecutionException;
32  import org.apache.maven.plugin.MojoExecutionException;
33  import org.apache.maven.plugin.MojoFailureException;
34  import org.apache.maven.plugin.PluginContainerException;
35  import org.apache.maven.plugin.PluginExecutionException;
36  import org.apache.maven.project.ProjectBuildingException;
37  import org.apache.maven.project.ProjectBuildingResult;
38  import org.codehaus.plexus.component.annotations.Component;
39  import org.codehaus.plexus.util.StringUtils;
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  @Component( role = ExceptionHandler.class )
90  public class DefaultExceptionHandler
91      implements ExceptionHandler
92  {
93  
94      public ExceptionSummary handleException( Throwable exception )
95      {
96          return handle( "", exception );
97      }
98  
99      private ExceptionSummary handle( String message, Throwable exception )
100     {
101         String reference = getReference( exception );
102 
103         List<ExceptionSummary> children = null;
104 
105         if ( exception instanceof ProjectBuildingException )
106         {
107             List<ProjectBuildingResult> results = ( (ProjectBuildingException) exception ).getResults();
108 
109             children = new ArrayList<>();
110 
111             for ( ProjectBuildingResult result : results )
112             {
113                 ExceptionSummary child = handle( result );
114                 if ( child != null )
115                 {
116                     children.add( child );
117                 }
118             }
119 
120             message = "The build could not read " + children.size() + " project" + ( children.size() == 1 ? "" : "s" );
121         }
122         else
123         {
124             message = getMessage( message, exception );
125         }
126 
127         return new ExceptionSummary( exception, message, reference, children );
128     }
129 
130     private ExceptionSummary handle( ProjectBuildingResult result )
131     {
132         List<ExceptionSummary> children = new ArrayList<>();
133 
134         for ( ModelProblem problem : result.getProblems() )
135         {
136             ExceptionSummary child = handle( problem, result.getProjectId() );
137             if ( child != null )
138             {
139                 children.add( child );
140             }
141         }
142 
143         if ( children.isEmpty() )
144         {
145             return null;
146         }
147 
148         String message =
149             "\nThe project " + result.getProjectId() + " (" + result.getPomFile() + ") has "
150                 + children.size() + " error" + ( children.size() == 1 ? "" : "s" );
151 
152         return new ExceptionSummary( null, message, null, children );
153     }
154 
155     private ExceptionSummary handle( ModelProblem problem, String projectId )
156     {
157         if ( ModelProblem.Severity.ERROR.compareTo( problem.getSeverity() ) >= 0 )
158         {
159             String message = problem.getMessage();
160 
161             String location = ModelProblemUtils.formatLocation( problem, projectId );
162 
163             if ( StringUtils.isNotEmpty( location ) )
164             {
165                 message += " @ " + location;
166             }
167 
168             return handle( message, problem.getException() );
169         }
170         else
171         {
172             return null;
173         }
174     }
175 
176     private String getReference( Throwable exception )
177     {
178         String reference = "";
179 
180         if ( exception != null )
181         {
182             if ( exception instanceof MojoExecutionException )
183             {
184                 reference = MojoExecutionException.class.getSimpleName();
185 
186                 Throwable cause = exception.getCause();
187                 if ( cause instanceof IOException )
188                 {
189                     cause = cause.getCause();
190                     if ( cause instanceof ConnectException )
191                     {
192                         reference = ConnectException.class.getSimpleName();
193                     }
194                 }
195             }
196             else if ( exception instanceof MojoFailureException )
197             {
198                 reference = MojoFailureException.class.getSimpleName();
199             }
200             else if ( exception instanceof LinkageError )
201             {
202                 reference = LinkageError.class.getSimpleName();
203             }
204             else if ( exception instanceof PluginExecutionException )
205             {
206                 Throwable cause = exception.getCause();
207 
208                 if ( cause instanceof PluginContainerException )
209                 {
210                     Throwable cause2 = cause.getCause();
211 
212                     if ( cause2 instanceof NoClassDefFoundError )
213                     {
214                         String message = cause2.getMessage();
215                         if ( message != null && message.contains( "org/sonatype/aether/" ) )
216                         {
217                             reference = "AetherClassNotFound";
218                         }
219                     }
220                 }
221 
222                 if ( StringUtils.isEmpty( reference ) )
223                 {
224                     reference = getReference( cause );
225                 }
226 
227                 if ( StringUtils.isEmpty( reference ) )
228                 {
229                     reference = exception.getClass().getSimpleName();
230                 }
231             }
232             else if ( exception instanceof LifecycleExecutionException )
233             {
234                 reference = getReference( exception.getCause() );
235             }
236             else if ( isNoteworthyException( exception ) )
237             {
238                 reference = exception.getClass().getSimpleName();
239             }
240         }
241 
242         if ( StringUtils.isNotEmpty( reference ) && !reference.startsWith( "http:" ) )
243         {
244             reference = "http://cwiki.apache.org/confluence/display/MAVEN/" + reference;
245         }
246 
247         return reference;
248     }
249 
250     private boolean isNoteworthyException( Throwable exception )
251     {
252         if ( exception == null )
253         {
254             return false;
255         }
256         else if ( exception instanceof Error )
257         {
258             return true;
259         }
260         else if ( exception instanceof RuntimeException )
261         {
262             return false;
263         }
264         else if ( exception.getClass().getName().startsWith( "java" ) )
265         {
266             return false;
267         }
268         return true;
269     }
270 
271     private String getMessage( String message, Throwable exception )
272     {
273         String fullMessage = ( message != null ) ? message : "";
274 
275         for ( Throwable t = exception; t != null; t = t.getCause() )
276         {
277             String exceptionMessage = t.getMessage();
278 
279             if ( t instanceof AbstractMojoExecutionException )
280             {
281                 String longMessage = ( (AbstractMojoExecutionException) t ).getLongMessage();
282                 if ( StringUtils.isNotEmpty( longMessage ) )
283                 {
284                     if ( StringUtils.isEmpty( exceptionMessage ) || longMessage.contains( exceptionMessage ) )
285                     {
286                         exceptionMessage = longMessage;
287                     }
288                     else if ( !exceptionMessage.contains( longMessage ) )
289                     {
290                         exceptionMessage = join( exceptionMessage, '\n' + longMessage );
291                     }
292                 }
293             }
294 
295             if ( StringUtils.isEmpty( exceptionMessage ) )
296             {
297                 exceptionMessage = t.getClass().getSimpleName();
298             }
299 
300             if ( t instanceof UnknownHostException && !fullMessage.contains( "host" ) )
301             {
302                 fullMessage = join( fullMessage, "Unknown host " + exceptionMessage );
303             }
304             else if ( !fullMessage.contains( exceptionMessage ) )
305             {
306                 fullMessage = join( fullMessage, exceptionMessage );
307             }
308         }
309 
310         return fullMessage.trim();
311     }
312 
313     private String join( String message1, String message2 )
314     {
315         String message = "";
316 
317         if ( StringUtils.isNotEmpty( message1 ) )
318         {
319             message = message1.trim();
320         }
321 
322         if ( StringUtils.isNotEmpty( message2 ) )
323         {
324             if ( StringUtils.isNotEmpty( message ) )
325             {
326                 if ( message.endsWith( "." ) || message.endsWith( "!" ) || message.endsWith( ":" ) )
327                 {
328                     message += " ";
329                 }
330                 else
331                 {
332                     message += ": ";
333                 }
334             }
335 
336             message += message2;
337         }
338 
339         return message;
340     }
341 
342 }