o Cleaned up a couple of things in repoclean that broke with the last update.

o Added concept of ErrorDiagnoser to help interpret errors and provide user feedback
o Added PluginParameterException to provide richer information than simply PluginConfigurationException (it's derived from PluginConfigurationException)
o Added implementations of ErrorDiagnoser for artifact resolution and plugin configuration handling.
o Modified DefaultMaven's logFailure(..) method to use errorDiagnosers Map (injected via Plexus)

I approached the plugin parameter expression/name feedback in this way, as it seems like a general pattern for interpreting errors without embedding this logic deep within the app itself. Feel free to rollback if this causes issues.

git-svn-id: https://svn.apache.org/repos/asf/maven/components/trunk@169379 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
John Dennis Casey 2005-05-09 23:30:01 +00:00
parent f3fa32eebe
commit 7eda955b1d
13 changed files with 765 additions and 32 deletions

View File

@ -37,6 +37,7 @@ import org.apache.maven.settings.Mirror;
import org.apache.maven.settings.Proxy;
import org.apache.maven.settings.Server;
import org.apache.maven.settings.Settings;
import org.apache.maven.usability.ErrorDiagnoser;
import org.codehaus.plexus.PlexusConstants;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
@ -53,6 +54,7 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:jason@maven.org">Jason van Zyl </a>
@ -73,6 +75,8 @@ public class DefaultMaven
protected LifecycleExecutor lifecycleExecutor;
protected PlexusContainer container;
protected Map errorDiagnosers;
// ----------------------------------------------------------------------
// Project execution
@ -376,22 +380,28 @@ public class DefaultMaven
getLogger().info( "BUILD FAILURE" );
line();
getLogger().info( "Reason: " + e.getMessage() );
getLogger().info( "Found these embedded error messages:\n" );
Throwable cause = e.getCause();
int depth = 0;
while( cause != null )
String message = null;
if(errorDiagnosers != null)
{
getLogger().info( "\t[" + ( depth++ ) + "] " + cause.getMessage() );
cause = cause.getCause();
for ( Iterator it = errorDiagnosers.values().iterator(); it.hasNext(); )
{
ErrorDiagnoser diagnoser = (ErrorDiagnoser) it.next();
if( diagnoser.canDiagnose( e ) )
{
message = diagnoser.diagnose( e );
}
}
}
if( message == null )
{
message = "Reason: " + e.getMessage();
}
getLogger().info( message );
line();
if ( longMessage != null )

View File

@ -60,6 +60,7 @@ import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@ -592,6 +593,8 @@ public class DefaultPluginManager
{
List parameters = goal.getParameters();
List invalidParameters = new ArrayList();
for ( int i = 0; i < parameters.size(); i++ )
{
Parameter parameter = (Parameter) parameters.get( i );
@ -702,11 +705,15 @@ public class DefaultPluginManager
if ( value == null && parameter.isRequired() )
{
throw new PluginConfigurationException(
createPluginParameterRequiredMessage( goal, parameter, expression ) );
invalidParameters.add( parameter );
}
}
if( !invalidParameters.isEmpty() )
{
throw new PluginParameterException( goal, invalidParameters );
}
}
public static String createPluginParameterRequiredMessage( MojoDescriptor mojo, Parameter parameter,

View File

@ -0,0 +1,58 @@
package org.apache.maven.plugin;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import java.util.List;
/*
* Copyright 2001-2005 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
public class PluginParameterException
extends PluginConfigurationException
{
private final List parameters;
private final MojoDescriptor mojo;
public PluginParameterException( MojoDescriptor mojo, List parameters )
{
super( "Invalid or missing parameters: " + parameters + " for mojo: " + mojo.getRoleHint() );
this.mojo = mojo;
this.parameters = parameters;
}
public PluginParameterException( MojoDescriptor mojo, List parameters, Throwable cause )
{
super( "Invalid or missing parameters: " + parameters + " for mojo: " + mojo.getRoleHint(), cause );
this.mojo = mojo;
this.parameters = parameters;
}
public MojoDescriptor getMojoDescriptor()
{
return mojo;
}
public List getParameters()
{
return parameters;
}
}

View File

@ -0,0 +1,102 @@
package org.apache.maven.usability;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.artifact.resolver.TransitiveArtifactResolutionException;
import org.apache.maven.project.ProjectBuildingException;
/*
* Copyright 2001-2005 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
public class ArtifactResolverDiagnoser
implements ErrorDiagnoser
{
public boolean canDiagnose( Throwable error )
{
return error instanceof ArtifactResolutionException;
}
public String diagnose( Throwable error )
{
Throwable root = traverseToRoot( error );
String message = null;
if ( root instanceof ProjectBuildingException )
{
StringBuffer messageBuffer = new StringBuffer();
if ( causalityChainContains( error, TransitiveArtifactResolutionException.class ) )
{
messageBuffer
.append( "Error while transitively resolving artifacts (transitive path trace currently unavailable):\n\n" );
}
else
{
messageBuffer.append( "Error while resolving artifacts:\n\n" );
}
messageBuffer.append( "Root Error:\n " ).append( root.getMessage() );
message = messageBuffer.toString();
}
else
{
StringBuffer messageBuffer = new StringBuffer();
messageBuffer.append( "Main Error:\n " ).append( error.getMessage() );
messageBuffer.append( "\n\nRoot error:\n " ).append( root.getMessage() );
message = messageBuffer.toString();
}
return message;
}
private boolean causalityChainContains( Throwable error, Class errorClass )
{
Throwable cause = error;
boolean contains = false;
while ( cause != null )
{
if ( errorClass.isInstance( cause ) )
{
contains = true;
break;
}
cause = cause.getCause();
}
return contains;
}
private Throwable traverseToRoot( Throwable error )
{
Throwable potentialRoot = error;
while ( potentialRoot.getCause() != null )
{
potentialRoot = potentialRoot.getCause();
}
return potentialRoot;
}
}

View File

@ -0,0 +1,27 @@
package org.apache.maven.usability;
/*
* Copyright 2001-2005 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
public interface ErrorDiagnoser
{
public static final String ROLE = ErrorDiagnoser.class.getName();
public boolean canDiagnose( Throwable error );
public String diagnose( Throwable error );
}

View File

@ -0,0 +1,241 @@
package org.apache.maven.usability;
import org.apache.maven.plugin.PluginConfigurationException;
import org.apache.maven.plugin.PluginParameterException;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.plugin.descriptor.Parameter;
import org.codehaus.plexus.util.StringUtils;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/*
* Copyright 2001-2005 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
public class PluginConfigurationDiagnoser
implements ErrorDiagnoser
{
private static final List UNMODIFIABLE_EXPRESSIONS;
static
{
List exprs = new ArrayList();
exprs.add( "localRepository" );
exprs.add( "settings" );
exprs.add( "basedir" );
UNMODIFIABLE_EXPRESSIONS = exprs;
}
public boolean canDiagnose( Throwable error )
{
return error instanceof PluginConfigurationException;
}
public String diagnose( Throwable error )
{
if ( error instanceof PluginParameterException )
{
PluginParameterException exception = (PluginParameterException) error;
return buildDiagnosticMessage( exception );
}
else
{
return error.getMessage();
}
}
private String buildDiagnosticMessage( PluginParameterException exception )
{
StringBuffer messageBuffer = new StringBuffer();
List params = exception.getParameters();
MojoDescriptor mojo = exception.getMojoDescriptor();
messageBuffer.append( "One or more required plugin parameters are invalid/missing for \'" )
.append( mojo.getPluginDescriptor().getGoalPrefix() ).append( ":" ).append( mojo.getGoal() )
.append( "\'\n" );
int idx = 0;
for ( Iterator it = params.iterator(); it.hasNext(); )
{
Parameter param = (Parameter) it.next();
messageBuffer.append( "\n[" ).append( idx++ ).append( "] " );
decomposeParameterIntoUserInstructions( mojo, param, messageBuffer );
messageBuffer.append( "\n" );
}
return messageBuffer.toString();
}
private void decomposeParameterIntoUserInstructions( MojoDescriptor mojo, Parameter param,
StringBuffer messageBuffer )
{
String expression = param.getExpression();
if ( param.isEditable() )
{
messageBuffer.append( "specify configuration for <" + param.getName() + ">VALUE</" + param.getName() + ">" );
String alias = param.getAlias();
if ( StringUtils.isNotEmpty( alias ) )
{
messageBuffer.append( " (aliased as: <" + alias + ">VALUE</" + alias + ">)" );
}
messageBuffer.append( "\n inside the <configuration/> section for "
+ mojo.getPluginDescriptor().getArtifactId() );
}
if ( StringUtils.isEmpty( expression ) )
{
messageBuffer.append( "." );
}
else
{
StringBuffer expressionMessageBuffer = new StringBuffer();
if ( param.isEditable() )
{
expressionMessageBuffer.append( ", or\n " );
}
Matcher exprMatcher = Pattern.compile( "\\$\\{(.+)\\}" ).matcher( expression );
boolean unmodifiableElementsFound = false;
boolean activeElementsFound = false;
int elementCount = 0;
while ( exprMatcher.find() )
{
elementCount++;
activeElementsFound = true;
String subExpression = exprMatcher.group( 1 );
StringTokenizer expressionParts = new StringTokenizer( subExpression, "." );
String firstPart = expressionParts.nextToken();
if ( "project".equals( firstPart ) && expressionParts.hasMoreTokens() )
{
appendProjectSection( expressionParts, expressionMessageBuffer );
}
else if ( "reports".equals( firstPart ) )
{
expressionMessageBuffer
.append( "make sure the <reports/> section of the pom.xml contains valid report names\n" );
}
else if ( UNMODIFIABLE_EXPRESSIONS.contains( subExpression ) )
{
unmodifiableElementsFound = true;
}
else
{
expressionMessageBuffer.append( "Please provide the system property: " ).append( subExpression )
.append( "\n (specified as \'-D" + subExpression + "=VALUE\' on the command line)\n" );
}
}
if ( activeElementsFound )
{
messageBuffer.append( expressionMessageBuffer );
}
else
{
messageBuffer.append( " (found static expression: \'" + expression
+ "\' which may act as a default value).\n" );
}
if ( unmodifiableElementsFound )
{
if ( elementCount > 1 )
{
messageBuffer.append( " " );
}
messageBuffer
.append( "NOTE: One or more purely derived expression elements were detected in \'"
+ expression
+ "\'.\n If you continue to get this error after any other expression elements are specified correctly,"
+ "\n please report this issue to the Maven development team.\n" );
}
}
}
private void appendProjectSection( StringTokenizer expressionParts, StringBuffer messageBuffer )
{
messageBuffer.append( "check that the following section of the pom.xml is present and correct:\n\n" );
Stack nestedParts = new Stack();
String indentation = " ";
messageBuffer.append( indentation ).append( "<project>\n" );
nestedParts.push( "project" );
indentation += " ";
while ( expressionParts.hasMoreTokens() )
{
String nextPart = expressionParts.nextToken();
messageBuffer.append( indentation ).append( "<" ).append( nextPart );
if ( expressionParts.hasMoreTokens() )
{
messageBuffer.append( ">\n" );
indentation += " ";
nestedParts.push( nextPart );
}
else
{
messageBuffer.append( "/>\n" );
indentation = indentation.substring( 2 );
}
}
if ( !nestedParts.isEmpty() )
{
while ( nestedParts.size() > 0 )
{
String prevPart = (String) nestedParts.pop();
messageBuffer.append( indentation ).append( "</" ).append( prevPart ).append( ">\n" );
indentation = indentation.substring( 2 );
}
}
}
}

View File

@ -18,8 +18,32 @@
<requirement>
<role>org.apache.maven.lifecycle.LifecycleExecutor</role>
</requirement>
<requirement>
<role>org.apache.maven.usability.ErrorDiagnoser</role>
<field-name>errorDiagnosers</field-name>
</requirement>
</requirements>
</component>
<!--
|
|
|
-->
<component>
<role>org.apache.maven.usability.ErrorDiagnoser</role>
<role-hint>plugin-configuration</role-hint>
<implementation>org.apache.maven.usability.PluginConfigurationDiagnoser</implementation>
</component>
<!--
|
|
|
-->
<component>
<role>org.apache.maven.usability.ErrorDiagnoser</role>
<role-hint>artifact-resolution</role-hint>
<implementation>org.apache.maven.usability.ArtifactResolverDiagnoser</implementation>
</component>
<!--
|
|

View File

@ -0,0 +1,232 @@
package org.apache.maven.usability;
import org.apache.maven.plugin.PluginParameterException;
import org.apache.maven.plugin.descriptor.DuplicateParameterException;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.plugin.descriptor.Parameter;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import junit.framework.TestCase;
/*
* Copyright 2001-2005 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
public class PluginErrorDiagnoserTest
extends TestCase
{
private PluginParameterException buildException( String prefix, String goal, List params )
throws DuplicateParameterException
{
PluginDescriptor pluginDescriptor = new PluginDescriptor();
pluginDescriptor.setArtifactId( "maven-test-plugin" );
pluginDescriptor.setGroupId( "org.apache.maven.plugins" );
pluginDescriptor.setVersion( "1.0" );
pluginDescriptor.setGoalPrefix( prefix );
MojoDescriptor mojoDescriptor = new MojoDescriptor();
mojoDescriptor.setGoal( goal );
mojoDescriptor.setPluginDescriptor( pluginDescriptor );
mojoDescriptor.setParameters( params );
return new PluginParameterException( mojoDescriptor, params );
}
public void testShouldBeAbleToDiagnosePluginParameterExceptions()
throws DuplicateParameterException
{
Parameter param = new Parameter();
param.setName( "testName" );
param.setAlias( "testAlias" );
param.setExpression( "${project.build.finalName}" );
param.setEditable( true );
PluginParameterException error = buildException( "test", "test", Collections.singletonList( param ) );
assertTrue( new PluginConfigurationDiagnoser().canDiagnose( error ) );
}
public void testParamWithOneReportsExpressionAndOneProjectBasedExpression()
throws DuplicateParameterException
{
printMethodHeader();
List params = new ArrayList();
Parameter param = new Parameter();
param.setName( "param1" );
param.setExpression( "${reports}" );
param.setEditable( false );
params.add( param );
Parameter param2 = new Parameter();
param2.setName( "param2" );
param2.setExpression( "${project.build.finalName}" );
param2.setEditable( false );
params.add( param2 );
PluginParameterException error = buildException( "test", "test", params );
String userMessage = new PluginConfigurationDiagnoser().diagnose( error );
System.out.println( userMessage );
assertNotNull( userMessage );
}
public void testParamWithNonActiveExpression()
throws DuplicateParameterException
{
printMethodHeader();
Parameter param = new Parameter();
param.setName( "testName" );
param.setAlias( "testAlias" );
param.setExpression( "${project.build.finalName" );
param.setEditable( true );
PluginParameterException error = buildException( "test", "test", Collections.singletonList( param ) );
String userMessage = new PluginConfigurationDiagnoser().diagnose( error );
System.out.println( userMessage );
assertNotNull( userMessage );
}
public void testParamWithoutExpression()
throws DuplicateParameterException
{
printMethodHeader();
Parameter param = new Parameter();
param.setName( "testName" );
param.setAlias( "testAlias" );
param.setEditable( true );
PluginParameterException error = buildException( "test", "test", Collections.singletonList( param ) );
String userMessage = new PluginConfigurationDiagnoser().diagnose( error );
System.out.println( userMessage );
assertNotNull( userMessage );
}
public void testParamWithOneLocalRepositoryExpression()
throws DuplicateParameterException
{
printMethodHeader();
Parameter param = new Parameter();
param.setName( "testName" );
param.setAlias( "testAlias" );
param.setExpression( "${localRepository}" );
param.setEditable( false );
PluginParameterException error = buildException( "test", "test", Collections.singletonList( param ) );
String userMessage = new PluginConfigurationDiagnoser().diagnose( error );
System.out.println( userMessage );
assertNotNull( userMessage );
}
public void testParamWithOneSystemPropertyExpression()
throws DuplicateParameterException
{
printMethodHeader();
Parameter param = new Parameter();
param.setName( "testName" );
param.setAlias( "testAlias" );
param.setExpression( "${maven.mode.online}" );
param.setEditable( false );
PluginParameterException error = buildException( "test", "test", Collections.singletonList( param ) );
String userMessage = new PluginConfigurationDiagnoser().diagnose( error );
System.out.println( userMessage );
assertNotNull( userMessage );
}
public void testParamWithOneProjectBasedExpression()
throws DuplicateParameterException
{
printMethodHeader();
Parameter param = new Parameter();
param.setName( "testName" );
param.setAlias( "testAlias" );
param.setExpression( "${project.build.finalName}" );
param.setEditable( true );
PluginParameterException error = buildException( "test", "test", Collections.singletonList( param ) );
String userMessage = new PluginConfigurationDiagnoser().diagnose( error );
System.out.println( userMessage );
assertNotNull( userMessage );
}
public void testNonEditableParamWithOneProjectBasedExpression()
throws DuplicateParameterException
{
printMethodHeader();
Parameter param = new Parameter();
param.setName( "testName" );
param.setAlias( "testAlias" );
param.setExpression( "${project.build.finalName}" );
param.setEditable( false );
PluginParameterException error = buildException( "test", "test", Collections.singletonList( param ) );
String userMessage = new PluginConfigurationDiagnoser().diagnose( error );
System.out.println( userMessage );
assertNotNull( userMessage );
}
private void printMethodHeader()
{
IllegalArgumentException marker = new IllegalArgumentException();
System.out.println( "---------------------------------------------------------------------\n"
+ "Visual output for " + marker.getStackTrace()[1].getMethodName()
+ ":\n---------------------------------------------------------------------" );
}
}

View File

@ -155,4 +155,9 @@ public class Parameter
{
this.editable = editable;
}
public String toString()
{
return "Mojo parameter [name: \'" + getName() + "\'; alias: \'" + getAlias() + "\']";
}
}

View File

@ -277,7 +277,7 @@ public class DefaultMavenProjectBuilder
if ( validationResult.getMessageCount() > 0 )
{
throw new ProjectBuildingException( "Exception while building project from \'" + pomLocation + "\': " + validationResult.toString() );
throw new ProjectBuildingException( "Failed to validate POM for \'" + pomLocation + "\'.\n\n Reason(s):\n" + validationResult.render( " " ) );
}
return project;

View File

@ -58,26 +58,31 @@ public class ModelValidationResult
}
public String toString()
{
return render( "" );
}
public String render( String indentation )
{
if ( messages.size() == 0 )
{
return "There was no validation errors.";
return indentation + "There were no validation errors.";
}
StringBuffer message = new StringBuffer();
if ( messages.size() == 1 )
{
message.append( "There was 1 validation error: " );
}
else
{
message.append( "There was " + messages.size() + " validation errors: " + NEWLINE );
}
// if ( messages.size() == 1 )
// {
// message.append( "There was 1 validation error: " );
// }
// else
// {
// message.append( "There was " + messages.size() + " validation errors: " + NEWLINE );
// }
//
for ( int i = 0; i < messages.size(); i++ )
{
message.append( "#" + ( i + 1 ) + ": " + messages.get( i ).toString() + NEWLINE );
message.append( indentation + "[" + i + "] " + messages.get( i ).toString() + NEWLINE );
}
return message.toString();

View File

@ -1,6 +1,5 @@
package org.apache.maven.tools.repoclean.artifact.layout;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.metadata.ArtifactMetadata;
import org.apache.maven.artifact.repository.layout.ArtifactPathFormatException;
import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout;
@ -28,13 +27,11 @@ public class AlphaBridgingRepositoryLayout
public String pathOfMetadata( ArtifactMetadata metadata )
throws ArtifactPathFormatException
{
Artifact artifact = metadata.getArtifact();
StringBuffer path = new StringBuffer();
path.append( artifact.getGroupId().replace( '.', '/' ) ).append( '/' );
path.append( artifact.getArtifactId() ).append( '/' );
path.append( artifact.getBaseVersion() ).append( '/' );
path.append( metadata.getGroupId().replace( '.', '/' ) ).append( '/' );
path.append( metadata.getArtifactId() ).append( '/' );
path.append( metadata.getBaseVersion() ).append( '/' );
path.append( metadata.getFilename() );
return path.toString();

View File

@ -63,4 +63,29 @@ public class ProjectMetadata
{
// this should be immutable...
}
public boolean exists()
{
return false;
}
public String getGroupId()
{
return null;
}
public String getArtifactId()
{
return null;
}
public String getVersion()
{
return null;
}
public String getBaseVersion()
{
return null;
}
}