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