001    package 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    
022    import java.net.UnknownHostException;
023    import java.util.ArrayList;
024    import java.util.List;
025    
026    import org.apache.maven.lifecycle.LifecycleExecutionException;
027    import org.apache.maven.model.building.ModelProblem;
028    import org.apache.maven.model.building.ModelProblemUtils;
029    import org.apache.maven.plugin.AbstractMojoExecutionException;
030    import org.apache.maven.plugin.MojoExecutionException;
031    import org.apache.maven.plugin.MojoFailureException;
032    import org.apache.maven.plugin.PluginExecutionException;
033    import org.apache.maven.project.ProjectBuildingException;
034    import org.apache.maven.project.ProjectBuildingResult;
035    import org.codehaus.plexus.component.annotations.Component;
036    import org.codehaus.plexus.util.StringUtils;
037    
038    /*
039    
040    - test projects for each of these
041    - how to categorize the problems so that the id of the problem can be match to a page with descriptive help and the test
042      project
043    - nice little sample projects that could be run in the core as well as integration tests
044    
045    All Possible Errors
046    - invalid lifecycle phase (maybe same as bad CLI param, though you were talking about embedder too)
047    - <module> specified is not found
048    - malformed settings
049    - malformed POM
050    - local repository not writable
051    - remote repositories not available
052    - artifact metadata missing
053    - extension metadata missing
054    - extension artifact missing
055    - artifact metadata retrieval problem
056    - version range violation
057    - circular dependency
058    - artifact missing
059    - artifact retrieval exception
060    - md5 checksum doesn't match for local artifact, need to redownload this
061    - POM doesn't exist for a goal that requires one
062    - parent POM missing (in both the repository + relative path)
063    - component not found
064    
065    Plugins:
066    - plugin metadata missing
067    - plugin metadata retrieval problem
068    - plugin artifact missing
069    - plugin artifact retrieval problem
070    - plugin dependency metadata missing
071    - plugin dependency metadata retrieval problem
072    - plugin configuration problem
073    - plugin execution failure due to something that is know to possibly go wrong (like compilation failure)
074    - plugin execution error due to something that is not expected to go wrong (the compiler executable missing)
075    - asking to use a plugin for which you do not have a version defined - tools to easily select versions
076    - goal not found in a plugin (probably could list the ones that are)
077    
078     */
079    
080    //PluginNotFoundException, PluginResolutionException, PluginDescriptorParsingException, CycleDetectedInPluginGraphException;
081    
082    @Component( role = ExceptionHandler.class )
083    public class DefaultExceptionHandler
084        implements ExceptionHandler
085    {
086    
087        public ExceptionSummary handleException( Throwable exception )
088        {
089            return handle( "", exception );
090        }
091    
092        private ExceptionSummary handle( String message, Throwable exception )
093        {
094            String reference = getReference( exception );
095    
096            List<ExceptionSummary> children = null;
097    
098            if ( exception instanceof ProjectBuildingException )
099            {
100                List<ProjectBuildingResult> results = ( (ProjectBuildingException) exception ).getResults();
101    
102                children = new ArrayList<ExceptionSummary>();
103    
104                for ( ProjectBuildingResult result : results )
105                {
106                    ExceptionSummary child = handle( result );
107                    if ( child != null )
108                    {
109                        children.add( child );
110                    }
111                }
112    
113                message = "The build could not read " + children.size() + " project" + ( children.size() == 1 ? "" : "s" );
114            }
115            else
116            {
117                message = getMessage( message, exception );
118            }
119    
120            return new ExceptionSummary( exception, message, reference, children );
121        }
122    
123        private ExceptionSummary handle( ProjectBuildingResult result )
124        {
125            List<ExceptionSummary> children = new ArrayList<ExceptionSummary>();
126    
127            for ( ModelProblem problem : result.getProblems() )
128            {
129                ExceptionSummary child = handle( problem, result.getProjectId() );
130                if ( child != null )
131                {
132                    children.add( child );
133                }
134            }
135    
136            if ( children.isEmpty() )
137            {
138                return null;
139            }
140    
141            String message =
142                "\nThe project " + result.getProjectId() + " (" + result.getPomFile() + ") has "
143                    + children.size() + " error" + ( children.size() == 1 ? "" : "s" );
144    
145            return new ExceptionSummary( null, message, null, children );
146        }
147    
148        private ExceptionSummary handle( ModelProblem problem, String projectId )
149        {
150            if ( ModelProblem.Severity.ERROR.compareTo( problem.getSeverity() ) >= 0 )
151            {
152                String message = problem.getMessage();
153    
154                String location = ModelProblemUtils.formatLocation( problem, projectId );
155    
156                if ( StringUtils.isNotEmpty( location ) )
157                {
158                    message += " @ " + location;
159                }
160    
161                return handle( message, problem.getException() );
162            }
163            else
164            {
165                return null;
166            }
167        }
168    
169        private String getReference( Throwable exception )
170        {
171            String reference = "";
172    
173            if ( exception != null )
174            {
175                if ( exception instanceof MojoExecutionException )
176                {
177                    reference = MojoExecutionException.class.getSimpleName();
178                }
179                else if ( exception instanceof MojoFailureException )
180                {
181                    reference = MojoFailureException.class.getSimpleName();
182                }
183                else if ( exception instanceof LinkageError )
184                {
185                    reference = LinkageError.class.getSimpleName();
186                }
187                else if ( exception instanceof PluginExecutionException )
188                {
189                    reference = getReference( exception.getCause() );
190    
191                    if ( StringUtils.isEmpty( reference ) )
192                    {
193                        reference = exception.getClass().getSimpleName();
194                    }
195                }
196                else if ( exception instanceof LifecycleExecutionException )
197                {
198                    reference = getReference( exception.getCause() );
199                }
200                else if ( isNoteworthyException( exception ) )
201                {
202                    reference = exception.getClass().getSimpleName();
203                }
204            }
205    
206            if ( StringUtils.isNotEmpty( reference ) && !reference.startsWith( "http:" ) )
207            {
208                reference = "http://cwiki.apache.org/confluence/display/MAVEN/" + reference;
209            }
210    
211            return reference;
212        }
213    
214        private boolean isNoteworthyException( Throwable exception )
215        {
216            if ( exception == null )
217            {
218                return false;
219            }
220            else if ( exception instanceof Error )
221            {
222                return true;
223            }
224            else if ( exception instanceof RuntimeException )
225            {
226                return false;
227            }
228            else if ( exception.getClass().getName().startsWith( "java" ) )
229            {
230                return false;
231            }
232            return true;
233        }
234    
235        private String getMessage( String message, Throwable exception )
236        {
237            String fullMessage = ( message != null ) ? message : "";
238    
239            for ( Throwable t = exception; t != null; t = t.getCause() )
240            {
241                String exceptionMessage = t.getMessage();
242    
243                if ( t instanceof AbstractMojoExecutionException )
244                {
245                    String longMessage = ( (AbstractMojoExecutionException) t ).getLongMessage();
246                    if ( StringUtils.isNotEmpty( longMessage ) )
247                    {
248                        if ( StringUtils.isEmpty( exceptionMessage ) || longMessage.contains( exceptionMessage ) )
249                        {
250                            exceptionMessage = longMessage;
251                        }
252                        else if ( !exceptionMessage.contains( longMessage ) )
253                        {
254                            exceptionMessage = join( exceptionMessage, '\n' + longMessage );
255                        }
256                    }
257                }
258    
259                if ( StringUtils.isEmpty( exceptionMessage ) )
260                {
261                    exceptionMessage = t.getClass().getSimpleName();
262                }
263    
264                if ( t instanceof UnknownHostException && !fullMessage.contains( "host" ) )
265                {
266                    fullMessage = join( fullMessage, "Unknown host " + exceptionMessage );
267                }
268                else if ( !fullMessage.contains( exceptionMessage ) )
269                {
270                    fullMessage = join( fullMessage, exceptionMessage );
271                }
272            }
273    
274            return fullMessage.trim();
275        }
276    
277        private String join( String message1, String message2 )
278        {
279            String message = "";
280    
281            if ( StringUtils.isNotEmpty( message1 ) )
282            {
283                message = message1.trim();
284            }
285    
286            if ( StringUtils.isNotEmpty( message2 ) )
287            {
288                if ( StringUtils.isNotEmpty( message ) )
289                {
290                    if ( message.endsWith( "." ) || message.endsWith( "!" ) || message.endsWith( ":" ) )
291                    {
292                        message += " ";
293                    }
294                    else
295                    {
296                        message += ": ";
297                    }
298                }
299    
300                message += message2;
301            }
302    
303            return message;
304        }
305    
306    }