HelpMojo.java

  1. package org.owasp.dependency_check_maven;

  2. import org.apache.maven.plugin.AbstractMojo;
  3. import org.apache.maven.plugin.MojoExecutionException;
  4. import org.apache.maven.plugins.annotations.Mojo;
  5. import org.apache.maven.plugins.annotations.Parameter;

  6. import org.w3c.dom.Document;
  7. import org.w3c.dom.Element;
  8. import org.w3c.dom.Node;
  9. import org.w3c.dom.NodeList;
  10. import org.xml.sax.SAXException;

  11. import javax.xml.parsers.DocumentBuilder;
  12. import javax.xml.parsers.DocumentBuilderFactory;
  13. import javax.xml.parsers.ParserConfigurationException;
  14. import java.io.IOException;
  15. import java.io.InputStream;
  16. import java.util.ArrayList;
  17. import java.util.List;

  18. /**
  19.  * Display help information on dependency-check-maven.<br>
  20.  * Call <code>mvn dependency-check:help -Ddetail=true -Dgoal=&lt;goal-name&gt;</code> to display parameter details.
  21.  * @author maven-plugin-tools
  22.  */
  23. @Mojo( name = "help", requiresProject = false, threadSafe = true )
  24. public class HelpMojo
  25.     extends AbstractMojo
  26. {
  27.     /**
  28.      * If <code>true</code>, display all settable properties for each goal.
  29.      *
  30.      */
  31.     @Parameter( property = "detail", defaultValue = "false" )
  32.     private boolean detail;

  33.     /**
  34.      * The name of the goal for which to show help. If unspecified, all goals will be displayed.
  35.      *
  36.      */
  37.     @Parameter( property = "goal" )
  38.     private java.lang.String goal;

  39.     /**
  40.      * The maximum length of a display line, should be positive.
  41.      *
  42.      */
  43.     @Parameter( property = "lineLength", defaultValue = "80" )
  44.     private int lineLength;

  45.     /**
  46.      * The number of spaces per indentation level, should be positive.
  47.      *
  48.      */
  49.     @Parameter( property = "indentSize", defaultValue = "2" )
  50.     private int indentSize;

  51.     // /META-INF/maven/<groupId>/<artifactId>/plugin-help.xml
  52.     private static final String PLUGIN_HELP_PATH =
  53.                     "/META-INF/maven/org.owasp/dependency-check-maven/plugin-help.xml";

  54.     private static final int DEFAULT_LINE_LENGTH = 80;

  55.     private Document build()
  56.         throws MojoExecutionException
  57.     {
  58.         getLog().debug( "load plugin-help.xml: " + PLUGIN_HELP_PATH );
  59.         try ( InputStream is = getClass().getResourceAsStream( PLUGIN_HELP_PATH ) )
  60.         {
  61.             if ( is == null )
  62.             {
  63.                 throw new MojoExecutionException( "Could not find plugin descriptor at " + PLUGIN_HELP_PATH );
  64.             }
  65.             DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
  66.             DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
  67.             return dBuilder.parse( is );
  68.         }
  69.         catch ( IOException e )
  70.         {
  71.             throw new MojoExecutionException( e.getMessage(), e );
  72.         }
  73.         catch ( ParserConfigurationException e )
  74.         {
  75.             throw new MojoExecutionException( e.getMessage(), e );
  76.         }
  77.         catch ( SAXException e )
  78.         {
  79.             throw new MojoExecutionException( e.getMessage(), e );
  80.         }
  81.     }

  82.     /**
  83.      * {@inheritDoc}
  84.      */
  85.     @Override
  86.     public void execute()
  87.         throws MojoExecutionException
  88.     {
  89.         if ( lineLength <= 0 )
  90.         {
  91.             getLog().warn( "The parameter 'lineLength' should be positive, using '80' as default." );
  92.             lineLength = DEFAULT_LINE_LENGTH;
  93.         }
  94.         if ( indentSize <= 0 )
  95.         {
  96.             getLog().warn( "The parameter 'indentSize' should be positive, using '2' as default." );
  97.             indentSize = 2;
  98.         }

  99.         Document doc = build();

  100.         StringBuilder sb = new StringBuilder();
  101.         Node plugin = getSingleChild( doc, "plugin" );


  102.         String name = getValue( plugin, "name" );
  103.         String version = getValue( plugin, "version" );
  104.         String id = getValue( plugin, "groupId" ) + ":" + getValue( plugin, "artifactId" ) + ":" + version;
  105.         if ( isNotEmpty( name ) && !name.contains( id ) )
  106.         {
  107.             append( sb, name + " " + version, 0 );
  108.         }
  109.         else
  110.         {
  111.             if ( isNotEmpty( name ) )
  112.             {
  113.                 append( sb, name, 0 );
  114.             }
  115.             else
  116.             {
  117.                 append( sb, id, 0 );
  118.             }
  119.         }
  120.         append( sb, getValue( plugin, "description" ), 1 );
  121.         append( sb, "", 0 );

  122.         //<goalPrefix>plugin</goalPrefix>
  123.         String goalPrefix = getValue( plugin, "goalPrefix" );

  124.         Node mojos1 = getSingleChild( plugin, "mojos" );

  125.         List<Node> mojos = findNamedChild( mojos1, "mojo" );

  126.         if ( goal == null || goal.length() <= 0 )
  127.         {
  128.             append( sb, "This plugin has " + mojos.size() + ( mojos.size() > 1 ? " goals:" : " goal:" ), 0 );
  129.             append( sb, "", 0 );
  130.         }

  131.         for ( Node mojo : mojos )
  132.         {
  133.             writeGoal( sb, goalPrefix, (Element) mojo );
  134.         }

  135.         if ( getLog().isInfoEnabled() )
  136.         {
  137.             getLog().info( sb.toString() );
  138.         }
  139.     }


  140.     private static boolean isNotEmpty( String string )
  141.     {
  142.         return string != null && string.length() > 0;
  143.     }

  144.     private static String getValue( Node node, String elementName )
  145.         throws MojoExecutionException
  146.     {
  147.         return getSingleChild( node, elementName ).getTextContent();
  148.     }

  149.     private static Node getSingleChild( Node node, String elementName )
  150.         throws MojoExecutionException
  151.     {
  152.         List<Node> namedChild = findNamedChild( node, elementName );
  153.         if ( namedChild.isEmpty() )
  154.         {
  155.             throw new MojoExecutionException( "Could not find " + elementName + " in plugin-help.xml" );
  156.         }
  157.         if ( namedChild.size() > 1 )
  158.         {
  159.             throw new MojoExecutionException( "Multiple " + elementName + " in plugin-help.xml" );
  160.         }
  161.         return namedChild.get( 0 );
  162.     }

  163.     private static List<Node> findNamedChild( Node node, String elementName )
  164.     {
  165.         List<Node> result = new ArrayList<Node>();
  166.         NodeList childNodes = node.getChildNodes();
  167.         for ( int i = 0; i < childNodes.getLength(); i++ )
  168.         {
  169.             Node item = childNodes.item( i );
  170.             if ( elementName.equals( item.getNodeName() ) )
  171.             {
  172.                 result.add( item );
  173.             }
  174.         }
  175.         return result;
  176.     }

  177.     private static Node findSingleChild( Node node, String elementName )
  178.         throws MojoExecutionException
  179.     {
  180.         List<Node> elementsByTagName = findNamedChild( node, elementName );
  181.         if ( elementsByTagName.isEmpty() )
  182.         {
  183.             return null;
  184.         }
  185.         if ( elementsByTagName.size() > 1 )
  186.         {
  187.             throw new MojoExecutionException( "Multiple " + elementName + "in plugin-help.xml" );
  188.         }
  189.         return elementsByTagName.get( 0 );
  190.     }

  191.     private void writeGoal( StringBuilder sb, String goalPrefix, Element mojo )
  192.         throws MojoExecutionException
  193.     {
  194.         String mojoGoal = getValue( mojo, "goal" );
  195.         Node configurationElement = findSingleChild( mojo, "configuration" );
  196.         Node description = findSingleChild( mojo, "description" );
  197.         if ( goal == null || goal.length() <= 0 || mojoGoal.equals( goal ) )
  198.         {
  199.             append( sb, goalPrefix + ":" + mojoGoal, 0 );
  200.             Node deprecated = findSingleChild( mojo, "deprecated" );
  201.             if ( ( deprecated != null ) && isNotEmpty( deprecated.getTextContent() ) )
  202.             {
  203.                 append( sb, "Deprecated. " + deprecated.getTextContent(), 1 );
  204.                 if ( detail && description != null )
  205.                 {
  206.                     append( sb, "", 0 );
  207.                     append( sb, description.getTextContent(), 1 );
  208.                 }
  209.             }
  210.             else if ( description != null )
  211.             {
  212.                 append( sb, description.getTextContent(), 1 );
  213.             }
  214.             append( sb, "", 0 );

  215.             if ( detail )
  216.             {
  217.                 Node parametersNode = getSingleChild( mojo, "parameters" );
  218.                 List<Node> parameters = findNamedChild( parametersNode, "parameter" );
  219.                 append( sb, "Available parameters:", 1 );
  220.                 append( sb, "", 0 );

  221.                 for ( Node parameter : parameters )
  222.                 {
  223.                     writeParameter( sb, parameter, configurationElement );
  224.                 }
  225.             }
  226.         }
  227.     }

  228.     private void writeParameter( StringBuilder sb, Node parameter, Node configurationElement )
  229.         throws MojoExecutionException
  230.     {
  231.         String parameterName = getValue( parameter, "name" );
  232.         String parameterDescription = getValue( parameter, "description" );

  233.         Element fieldConfigurationElement = null;
  234.         if ( configurationElement != null )
  235.         {
  236.           fieldConfigurationElement =  (Element) findSingleChild( configurationElement, parameterName );
  237.         }

  238.         String parameterDefaultValue = "";
  239.         if ( fieldConfigurationElement != null && fieldConfigurationElement.hasAttribute( "default-value" ) )
  240.         {
  241.             parameterDefaultValue = " (Default: " + fieldConfigurationElement.getAttribute( "default-value" ) + ")";
  242.         }
  243.         append( sb, parameterName + parameterDefaultValue, 2 );
  244.         Node deprecated = findSingleChild( parameter, "deprecated" );
  245.         if ( ( deprecated != null ) && isNotEmpty( deprecated.getTextContent() ) )
  246.         {
  247.             append( sb, "Deprecated. " + deprecated.getTextContent(), 3 );
  248.             append( sb, "", 0 );
  249.         }
  250.         if ( isNotEmpty( parameterDescription ) ) {
  251.             append( sb, parameterDescription, 3 );
  252.         }
  253.         if ( "true".equals( getValue( parameter, "required" ) ) )
  254.         {
  255.             append( sb, "Required: Yes", 3 );
  256.         }
  257.         if ( ( fieldConfigurationElement != null ) && isNotEmpty( fieldConfigurationElement.getTextContent() ) )
  258.         {
  259.             String property = getPropertyFromExpression( fieldConfigurationElement.getTextContent() );
  260.             append( sb, "User property: " + property, 3 );
  261.         }

  262.         append( sb, "", 0 );
  263.     }

  264.     /**
  265.      * <p>Repeat a String <code>n</code> times to form a new string.</p>
  266.      *
  267.      * @param str    String to repeat
  268.      * @param repeat number of times to repeat str
  269.      * @return String with repeated String
  270.      * @throws NegativeArraySizeException if <code>repeat &lt; 0</code>
  271.      * @throws NullPointerException       if str is <code>null</code>
  272.      */
  273.     private static String repeat( String str, int repeat )
  274.     {
  275.         StringBuilder buffer = new StringBuilder( repeat * str.length() );

  276.         for ( int i = 0; i < repeat; i++ )
  277.         {
  278.             buffer.append( str );
  279.         }

  280.         return buffer.toString();
  281.     }

  282.     /**
  283.      * Append a description to the buffer by respecting the indentSize and lineLength parameters.
  284.      * <b>Note</b>: The last character is always a new line.
  285.      *
  286.      * @param sb          The buffer to append the description, not <code>null</code>.
  287.      * @param description The description, not <code>null</code>.
  288.      * @param indent      The base indentation level of each line, must not be negative.
  289.      */
  290.     private void append( StringBuilder sb, String description, int indent )
  291.     {
  292.         for ( String line : toLines( description, indent, indentSize, lineLength ) )
  293.         {
  294.             sb.append( line ).append( '\n' );
  295.         }
  296.     }

  297.     /**
  298.      * Splits the specified text into lines of convenient display length.
  299.      *
  300.      * @param text       The text to split into lines, must not be <code>null</code>.
  301.      * @param indent     The base indentation level of each line, must not be negative.
  302.      * @param indentSize The size of each indentation, must not be negative.
  303.      * @param lineLength The length of the line, must not be negative.
  304.      * @return The sequence of display lines, never <code>null</code>.
  305.      * @throws NegativeArraySizeException if <code>indent &lt; 0</code>
  306.      */
  307.     private static List<String> toLines( String text, int indent, int indentSize, int lineLength )
  308.     {
  309.         List<String> lines = new ArrayList<String>();

  310.         String ind = repeat( "\t", indent );

  311.         String[] plainLines = text.split( "(\r\n)|(\r)|(\n)" );

  312.         for ( String plainLine : plainLines )
  313.         {
  314.             toLines( lines, ind + plainLine, indentSize, lineLength );
  315.         }

  316.         return lines;
  317.     }

  318.     /**
  319.      * Adds the specified line to the output sequence, performing line wrapping if necessary.
  320.      *
  321.      * @param lines      The sequence of display lines, must not be <code>null</code>.
  322.      * @param line       The line to add, must not be <code>null</code>.
  323.      * @param indentSize The size of each indentation, must not be negative.
  324.      * @param lineLength The length of the line, must not be negative.
  325.      */
  326.     private static void toLines( List<String> lines, String line, int indentSize, int lineLength )
  327.     {
  328.         int lineIndent = getIndentLevel( line );
  329.         StringBuilder buf = new StringBuilder( 256 );

  330.         String[] tokens = line.split( " +" );

  331.         for ( String token : tokens )
  332.         {
  333.             if ( buf.length() > 0 )
  334.             {
  335.                 if ( buf.length() + token.length() >= lineLength )
  336.                 {
  337.                     lines.add( buf.toString() );
  338.                     buf.setLength( 0 );
  339.                     buf.append( repeat( " ", lineIndent * indentSize ) );
  340.                 }
  341.                 else
  342.                 {
  343.                     buf.append( ' ' );
  344.                 }
  345.             }

  346.             for ( int j = 0; j < token.length(); j++ )
  347.             {
  348.                 char c = token.charAt( j );
  349.                 if ( c == '\t' )
  350.                 {
  351.                     buf.append( repeat( " ", indentSize - buf.length() % indentSize ) );
  352.                 }
  353.                 else if ( c == '\u00A0' )
  354.                 {
  355.                     buf.append( ' ' );
  356.                 }
  357.                 else
  358.                 {
  359.                     buf.append( c );
  360.                 }
  361.             }
  362.         }
  363.         lines.add( buf.toString() );
  364.     }

  365.     /**
  366.      * Gets the indentation level of the specified line.
  367.      *
  368.      * @param line The line whose indentation level should be retrieved, must not be <code>null</code>.
  369.      * @return The indentation level of the line.
  370.      */
  371.     private static int getIndentLevel( String line )
  372.     {
  373.         int level = 0;
  374.         for ( int i = 0; i < line.length() && line.charAt( i ) == '\t'; i++ )
  375.         {
  376.             level++;
  377.         }
  378.         for ( int i = level + 1; i <= level + 4 && i < line.length(); i++ )
  379.         {
  380.             if ( line.charAt( i ) == '\t' )
  381.             {
  382.                 level++;
  383.                 break;
  384.             }
  385.         }
  386.         return level;
  387.     }

  388.     private static String getPropertyFromExpression( String expression )
  389.     {
  390.         if ( expression != null && expression.startsWith( "${" ) && expression.endsWith( "}" )
  391.             && !expression.substring( 2 ).contains( "${" ) )
  392.         {
  393.             // expression="${xxx}" -> property="xxx"
  394.             return expression.substring( 2, expression.length() - 1 );
  395.         }
  396.         // no property can be extracted
  397.         return null;
  398.     }
  399. }