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