001package org.apache.maven.exception;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.IOException;
023import java.net.ConnectException;
024import java.net.UnknownHostException;
025import java.util.ArrayList;
026import java.util.List;
027
028import org.apache.maven.lifecycle.LifecycleExecutionException;
029import org.apache.maven.model.building.ModelProblem;
030import org.apache.maven.model.building.ModelProblemUtils;
031import org.apache.maven.plugin.AbstractMojoExecutionException;
032import org.apache.maven.plugin.MojoExecutionException;
033import org.apache.maven.plugin.MojoFailureException;
034import org.apache.maven.plugin.PluginContainerException;
035import org.apache.maven.plugin.PluginExecutionException;
036import org.apache.maven.project.ProjectBuildingException;
037import org.apache.maven.project.ProjectBuildingResult;
038import org.codehaus.plexus.component.annotations.Component;
039import org.codehaus.plexus.util.StringUtils;
040
041/*
042
043- test projects for each of these
044- how to categorize the problems so that the id of the problem can be match to a page with descriptive help and the test
045  project
046- nice little sample projects that could be run in the core as well as integration tests
047
048All Possible Errors
049- invalid lifecycle phase (maybe same as bad CLI param, though you were talking about embedder too)
050- <module> specified is not found
051- malformed settings
052- malformed POM
053- local repository not writable
054- remote repositories not available
055- artifact metadata missing
056- extension metadata missing
057- extension artifact missing
058- artifact metadata retrieval problem
059- version range violation
060- circular dependency
061- artifact missing
062- artifact retrieval exception
063- md5 checksum doesn't match for local artifact, need to redownload this
064- POM doesn't exist for a goal that requires one
065- parent POM missing (in both the repository + relative path)
066- component not found
067
068Plugins:
069- plugin metadata missing
070- plugin metadata retrieval problem
071- plugin artifact missing
072- plugin artifact retrieval problem
073- plugin dependency metadata missing
074- plugin dependency metadata retrieval problem
075- plugin configuration problem
076- plugin execution failure due to something that is know to possibly go wrong (like compilation failure)
077- plugin execution error due to something that is not expected to go wrong (the compiler executable missing)
078- asking to use a plugin for which you do not have a version defined - tools to easily select versions
079- goal not found in a plugin (probably could list the ones that are)
080
081 */
082
083// PluginNotFoundException, PluginResolutionException, PluginDescriptorParsingException,
084// CycleDetectedInPluginGraphException;
085
086@Component( role = ExceptionHandler.class )
087public class DefaultExceptionHandler
088    implements ExceptionHandler
089{
090
091    public ExceptionSummary handleException( Throwable exception )
092    {
093        return handle( "", exception );
094    }
095
096    private ExceptionSummary handle( String message, Throwable exception )
097    {
098        String reference = getReference( exception );
099
100        List<ExceptionSummary> children = null;
101
102        if ( exception instanceof ProjectBuildingException )
103        {
104            List<ProjectBuildingResult> results = ( (ProjectBuildingException) exception ).getResults();
105
106            children = new ArrayList<>();
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<>();
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}