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  @Component( role = ExceptionHandler.class )
87  public class DefaultExceptionHandler
88      implements ExceptionHandler
89  {
90  
91      public ExceptionSummary handleException( Throwable exception )
92      {
93          return handle( "", exception );
94      }
95  
96      private ExceptionSummary handle( String message, Throwable exception )
97      {
98          String reference = getReference( exception );
99  
100         List<ExceptionSummary> children = null;
101 
102         if ( exception instanceof ProjectBuildingException )
103         {
104             List<ProjectBuildingResult> results = ( (ProjectBuildingException) exception ).getResults();
105 
106             children = new ArrayList<ExceptionSummary>();
107 
108             for ( ProjectBuildingResult result : results )
109             {
110                 ExceptionSummary child = handle( result );
111                 if ( child != null )
112                 {
113                     children.add( child );
114                 }
115             }
116 
117             message = "The build could not read " + children.size() + " project" + ( children.size() == 1 ? "" : "s" );
118         }
119         else
120         {
121             message = getMessage( message, exception );
122         }
123 
124         return new ExceptionSummary( exception, message, reference, children );
125     }
126 
127     private ExceptionSummary handle( ProjectBuildingResult result )
128     {
129         List<ExceptionSummary> children = new ArrayList<ExceptionSummary>();
130 
131         for ( ModelProblem problem : result.getProblems() )
132         {
133             ExceptionSummary child = handle( problem, result.getProjectId() );
134             if ( child != null )
135             {
136                 children.add( child );
137             }
138         }
139 
140         if ( children.isEmpty() )
141         {
142             return null;
143         }
144 
145         String message =
146             "\nThe project " + result.getProjectId() + " (" + result.getPomFile() + ") has "
147                 + children.size() + " error" + ( children.size() == 1 ? "" : "s" );
148 
149         return new ExceptionSummary( null, message, null, children );
150     }
151 
152     private ExceptionSummary handle( ModelProblem problem, String projectId )
153     {
154         if ( ModelProblem.Severity.ERROR.compareTo( problem.getSeverity() ) >= 0 )
155         {
156             String message = problem.getMessage();
157 
158             String location = ModelProblemUtils.formatLocation( problem, projectId );
159 
160             if ( StringUtils.isNotEmpty( location ) )
161             {
162                 message += " @ " + location;
163             }
164 
165             return handle( message, problem.getException() );
166         }
167         else
168         {
169             return null;
170         }
171     }
172 
173     private String getReference( Throwable exception )
174     {
175         String reference = "";
176 
177         if ( exception != null )
178         {
179             if ( exception instanceof MojoExecutionException )
180             {
181                 reference = MojoExecutionException.class.getSimpleName();
182 
183                 Throwable cause = exception.getCause();
184                 if ( cause instanceof IOException )
185                 {
186                     cause = cause.getCause();
187                     if ( cause instanceof ConnectException )
188                     {
189                         reference = ConnectException.class.getSimpleName();
190                     }
191                 }
192             }
193             else if ( exception instanceof MojoFailureException )
194             {
195                 reference = MojoFailureException.class.getSimpleName();
196             }
197             else if ( exception instanceof LinkageError )
198             {
199                 reference = LinkageError.class.getSimpleName();
200             }
201             else if ( exception instanceof PluginExecutionException )
202             {
203                 Throwable cause = exception.getCause();
204 
205                 if ( cause instanceof PluginContainerException )
206                 {
207                     Throwable cause2 = cause.getCause();
208 
209                     if ( cause2 instanceof NoClassDefFoundError
210                         && cause2.getMessage().contains( "org/sonatype/aether/" ) )
211                     {
212                         reference = "AetherClassNotFound";
213                     }
214                 }
215 
216                 if ( StringUtils.isEmpty( reference ) )
217                 {
218                     reference = getReference( cause );
219                 }
220 
221                 if ( StringUtils.isEmpty( reference ) )
222                 {
223                     reference = exception.getClass().getSimpleName();
224                 }
225             }
226             else if ( exception instanceof LifecycleExecutionException )
227             {
228                 reference = getReference( exception.getCause() );
229             }
230             else if ( isNoteworthyException( exception ) )
231             {
232                 reference = exception.getClass().getSimpleName();
233             }
234         }
235 
236         if ( StringUtils.isNotEmpty( reference ) && !reference.startsWith( "http:" ) )
237         {
238             reference = "http://cwiki.apache.org/confluence/display/MAVEN/" + reference;
239         }
240 
241         return reference;
242     }
243 
244     private boolean isNoteworthyException( Throwable exception )
245     {
246         if ( exception == null )
247         {
248             return false;
249         }
250         else if ( exception instanceof Error )
251         {
252             return true;
253         }
254         else if ( exception instanceof RuntimeException )
255         {
256             return false;
257         }
258         else if ( exception.getClass().getName().startsWith( "java" ) )
259         {
260             return false;
261         }
262         return true;
263     }
264 
265     private String getMessage( String message, Throwable exception )
266     {
267         String fullMessage = ( message != null ) ? message : "";
268 
269         for ( Throwable t = exception; t != null; t = t.getCause() )
270         {
271             String exceptionMessage = t.getMessage();
272 
273             if ( t instanceof AbstractMojoExecutionException )
274             {
275                 String longMessage = ( (AbstractMojoExecutionException) t ).getLongMessage();
276                 if ( StringUtils.isNotEmpty( longMessage ) )
277                 {
278                     if ( StringUtils.isEmpty( exceptionMessage ) || longMessage.contains( exceptionMessage ) )
279                     {
280                         exceptionMessage = longMessage;
281                     }
282                     else if ( !exceptionMessage.contains( longMessage ) )
283                     {
284                         exceptionMessage = join( exceptionMessage, '\n' + longMessage );
285                     }
286                 }
287             }
288 
289             if ( StringUtils.isEmpty( exceptionMessage ) )
290             {
291                 exceptionMessage = t.getClass().getSimpleName();
292             }
293 
294             if ( t instanceof UnknownHostException && !fullMessage.contains( "host" ) )
295             {
296                 fullMessage = join( fullMessage, "Unknown host " + exceptionMessage );
297             }
298             else if ( !fullMessage.contains( exceptionMessage ) )
299             {
300                 fullMessage = join( fullMessage, exceptionMessage );
301             }
302         }
303 
304         return fullMessage.trim();
305     }
306 
307     private String join( String message1, String message2 )
308     {
309         String message = "";
310 
311         if ( StringUtils.isNotEmpty( message1 ) )
312         {
313             message = message1.trim();
314         }
315 
316         if ( StringUtils.isNotEmpty( message2 ) )
317         {
318             if ( StringUtils.isNotEmpty( message ) )
319             {
320                 if ( message.endsWith( "." ) || message.endsWith( "!" ) || message.endsWith( ":" ) )
321                 {
322                     message += " ";
323                 }
324                 else
325                 {
326                     message += ": ";
327                 }
328             }
329 
330             message += message2;
331         }
332 
333         return message;
334     }
335 
336 }