Menu

java.lang.ClassNotFoundException when trying to run custom rule

2018-11-24
2018-11-27
  • Jim Madrigal

    Jim Madrigal - 2018-11-24

    I followed the documentation here: https://pmd.sourceforge.io/pmd-4.3/howtowritearule.html, but when I get to the "Run PMD using your new ruleset" section, my custom class cannot be found. One thing that isn't clear in the documentation is where I should be creating the rule and ruleset files. In a new directory at the root of the maven project I want to analyze? Somewhere in the src/main/java directory? In a completely different maven project? Does the answer change if I ultimately want to be running this rule as part of my maven build?

    The latter isn't even kind enough to throw a ClassNotFoundException... It seems to just skip over my rule and continue on its way:

    [INFO] >>> maven-pmd-plugin:3.4:check (default) > :pmd @ CustomerService >>>
    [INFO]
    [INFO] --- maven-pmd-plugin:3.4:pmd (pmd) @ CustomerService ---
    [INFO]
    [INFO] <<< maven-pmd-plugin:3.4:check (default) < :pmd @ CustomerService <<<
    [INFO]
    [INFO]
    [INFO] --- maven-pmd-plugin:3.4:check (default) @ CustomerService ---
    
     
  • Andreas Dangel

    Andreas Dangel - 2018-11-25

    Hi Jim,

    please note, that the documentation you use, is very very old (version 4.3), PMD is now at version 6.9.0....

    The latest documentation can be found via https://pmd.github.io , so e.g. https://pmd.github.io/pmd-6.9.0/pmd_userdocs_extending_writing_pmd_rules.html

    Since you are using maven, that makes life much easier: I've prepare a sample project for creating a custom rule here: https://github.com/pmd/pmd-examples/tree/java
    This also shows you, how to use the new rule for analyzing the project.

    One thing that isn't clear in the documentation is where I should be creating the rule and
    ruleset files. In a new directory at the root of the maven project I want to analyze? Somewhere
    in the src/main/java directory? In a completely different maven project? Does the answer
    change if I ultimately want to be running this rule as part of my maven build?

    This is always a completely separate project. You have one project, that helps you building your main project (aka. "build-tools") and you have your main project, you want to analyze, where you use the build-tools-project. This build-tools-project contains the custom PMD enhancements as well as the ruleset.

    Hope that helps,
    Andreas

     
  • Jim Madrigal

    Jim Madrigal - 2018-11-25

    Thanks for the tips, and for putting together those examples... Forgive me if I'm slow but I'm not much of a maven expert. I put my custom rule and coresponding ruleset in their own project and built it into a jar. Then in my main project I added in the below to the pom.xml:

    <build>
       <plugins>
         <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-pmd-plugin</artifactId>
           <version>3.11.0</version>
           <dependencies>
             <dependency>
               <groupId>com.test.rules</groupId>
               <artifactId>pmd-rules</artifactId>
               <version>1.0-SNAPSHOT</version>
             </dependency>
           </dependencies>
         </plugin>
       </plugins>
     </build>
    

    The dependency seems to get resolved ok, but the custom rule doesn't seem to be evaluated. Any sort of mvn pmd:pmd or mvn clean verify or whatever else in the main project just results in this same dissapointing output:

    [INFO] >>> maven-pmd-plugin:3.11.0:check (default) > :pmd @ CustomerService >>>
    [INFO]
    [INFO] --- maven-pmd-plugin:3.11.0:pmd (pmd) @ CustomerService ---
    [INFO]
    [INFO] <<< maven-pmd-plugin:3.11.0:check (default) < :pmd @ CustomerService <<<
    [INFO]
    [INFO]
    [INFO] --- maven-pmd-plugin:3.11.0:check (default) @ CustomerService ---
    

    Any idea what I'm missing here? I revised my custom rule to just log something and add a violation, so it seems like it ought to be doing something.

     
  • Andreas Dangel

    Andreas Dangel - 2018-11-25

    Hi,
    what you need to do is: use your custom rule in a custom ruleset and use this ruleset when executing pmd :)

    So, you'll need to create a own ruleset and use this ruleset in the configuration of the maven-pmd-plugin via the <rulesets> tag.

    From the example:

    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-pmd-plugin</artifactId>
        <version>3.11.0</version>
        <executions>
            <execution>
                <phase>verify</phase>
                <goals>
                    <goal>pmd</goal>
                    <goal>cpd</goal>
                </goals>
            </execution>
        </executions>
        <configuration>
            <minimumTokens>100</minimumTokens>
            <targetJdk>11</targetJdk>
            <rulesets>
                <ruleset>custom-java-ruleset.xml</ruleset>
            </rulesets>
        </configuration>
        <dependencies>
            <dependency>
                <groupId>net.sourceforge.pmd.examples</groupId>
                <artifactId>pmd-java-custom</artifactId>
                <version>1.0.0-SNAPSHOT</version>
            </dependency>
        </dependencies>
    </plugin>
    
     
  • Jim Madrigal

    Jim Madrigal - 2018-11-25

    Hi Andreas,

    I appreciate you looking into this. Ultimately it just isn't working... I defined everything to match your project structure exactly, tried dozens of different ways of running it, and ultimately this just turned into a black hole. I still get java.lang.ClassNotFoundException: com.driftt.rules.UnsecuredEndpointRule when running via the CLI and no output when running via mvn pmd:pmd no matter where/how I define and configure all this stuff. FWIW I really like PMD (used it effectively with Ant/Apex), but it's been far too hard to implement in a Maven/Java world, where I am admittedly less experienced. Ultimately I took 5 minutes to write this rule that I think would be useful (detects unauthenticated endpoints in Dropwizard resources), and a couple days later I still haven't been able to run it a single time.

    package java.com.test.rules;
    
    import com.google.common.collect.ImmutableSet;
    import io.dropwizard.auth.Auth;
    import io.dropwizard.jersey.PATCH;
    import java.util.Set;
    import java.util.stream.IntStream;
    import javax.ws.rs.DELETE;
    import javax.ws.rs.GET;
    import javax.ws.rs.HEAD;
    import javax.ws.rs.POST;
    import javax.ws.rs.PUT;
    import net.sourceforge.pmd.lang.ast.*;
    import net.sourceforge.pmd.lang.java.ast.*;
    import net.sourceforge.pmd.lang.java.rule.*;
    
    public class UnsecuredEndpointRule extends AbstractJavaRule {
      static final Set<String> HTTP_VERBS = ImmutableSet.of(
          GET.class.getSimpleName(),
          POST.class.getSimpleName(),
          PATCH.class.getSimpleName(),
          PUT.class.getSimpleName(),
          DELETE.class.getSimpleName(),
          HEAD.class.getSimpleName());
      static final String AUTH_ANNOTATION = Auth.class.getSimpleName();
    
      public Object visit(ASTMethodDeclaration node, Object data) {
        Node parent = node.jjtGetParent();
        if(methodIsExposed(parent) && !methodIsAuthenticated(node)) {
          addViolation(data, node);
        }
        return super.visit(node,data);
      }
    
      private boolean methodIsExposed(Node node) {
        return IntStream.range(0, node.jjtGetNumChildren())
            .anyMatch(index -> {
              Node child = node.jjtGetChild(index);
              return child instanceof ASTAnnotation &&
                  HTTP_VERBS.contains(((ASTAnnotation) child).getAnnotationName());
            });
      }
    
      private boolean methodIsAuthenticated(Node node) {
        return node.findDescendantsOfType(ASTFormalParameter.class).stream()
            .filter(astFormalParameter -> astFormalParameter.findChildrenOfType(ASTAnnotation.class).stream()
                .filter(astAnnotation -> astAnnotation.getAnnotationName().equals(AUTH_ANNOTATION))
                .map(astAnnotation -> true)
                .findFirst()
                .orElse(false))
            .map(astFormalParameter -> true)
            .findFirst()
            .orElse(false);
      }
    }
    
     

    Last edit: Jim Madrigal 2018-11-25
  • Andreas Dangel

    Andreas Dangel - 2018-11-25

    Are you able to share your complete project? Especially the pom.xml filex are important...

    If you run PMD from CLI, then you need to add your custom rule to the classpath so that PMD can see it. Maybe the example without maven is helpful there: https://github.com/pmd/pmd-examples/tree/java-without-maven - you can use the environment variable CLASSPATH to add additional jars to the runtime classpath of PMD (or just copy the jar file into the lib folder...).

     
  • Jim Madrigal

    Jim Madrigal - 2018-11-26

    I decided to go ahead and try and get it working via the CLI, and was ultimately successful (adding the jar to the lib folder), which was pretty exciting. I'd still like to incorporate these checks into maven if possible, but unfortunately I can't share the full pom.xml in the service, but I'll post the build section I added. Here are the relevant snippets:

    in the service

    <build>
        <plugins>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-pmd-plugin</artifactId>
            <version>3.11.0</version>
            <executions>
              <execution>
                <phase>verify</phase>
                <goals>
                  <goal>check</goal>
                </goals>
              </execution>
            </executions>
            <configuration>
              <rulesets>
                <ruleset>ruleset.xml</ruleset> <!--this is defined in the other project-->
              </rulesets>
            </configuration>
            <dependencies>
              <dependency>
                <groupId>com.driftt.rules</groupId>
                <artifactId>pmd-rules</artifactId>
                <version>1.0-SNAPSHOT</version>
              </dependency>
            </dependencies>
          </plugin>
        </plugins>
      </build>
    

    in the custom rule's project

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
    
      <groupId>com.driftt.rules</groupId>
      <artifactId>pmd-rules</artifactId>
      <version>1.0-SNAPSHOT</version>
      <packaging>jar</packaging>
    
      <properties>
        <project.build.targetJdk>1.8</project.build.targetJdk>
      </properties>
    
      <dependencies>
        <dependency>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-pmd-plugin</artifactId>
          <version>3.11.0</version>
          <type>maven-plugin</type>
        </dependency>
      </dependencies>
    
      <build>
        <plugins>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.1</version>
            <configuration>
              <source>1.8</source>
              <target>1.8</target>
            </configuration>
          </plugin>
        </plugins>
      </build>
    </project>
    
     
  • Andreas Dangel

    Andreas Dangel - 2018-11-26

    Hi,
    if your custom rule is not executed from within maven, then we'll need to have a closer look at the maven-pmd-plugin configuration:

              <rulesets>
                <ruleset>ruleset.xml</ruleset> <!--this is defined in the other project-->
              </rulesets>
    

    Where is the file ruleset.xml exactly located? It should be in the project "pmd-rules" under the path src/main/resources/ruleset.xml, so that it is included in the pmd-rules-1.0-SNAPSHOT.jar.

    Also, please double check, that your new rule is included in this ruleset.xml.

     
  • Jim Madrigal

    Jim Madrigal - 2018-11-26

    Looks something like this:

    .
    +-- pmd-rules
    |   +-- pom.xml
    |   +-- src
    ||      +-- main
    |||         +-- java
    ||||            +-- com/driftt/rules/UnsecuredEndpointRule.java
    |||         +-- resources
    ||||            +-- ruleset.xml
    ||||            +-- com/driftt/rules/UnsecuredEndpointRule.xml
    +-- actual-service-directory
    |   +-- pom.xml
    

    I confirmed that the new rule is included in the ruleset.xml -- it is executed correctly when I specify the pmd-rules/src/main/resources/ruleset.xml as the ruleset in the CLI.

    apologies for the terrible formatting... hopefully it's at least clear :)

     

    Last edit: Jim Madrigal 2018-11-26
  • Andreas Dangel

    Andreas Dangel - 2018-11-26

    Ok, so if you are building your main project, the maven-pmd-plugin will copy the ruleset(s) it is using into the target folder of each module. Could you have a look at .../target/ruleset.xml, whether this is the correct ruleset?

    The notation <ruleset>ruleset.xml</ruleset> could be ambigous. To avoid this, you could move the ruleset in the "pmd-rules" project into a subpackage, e.g. into pmd-rules/src/main/resources/com/driftt/ruleset.xml and then use it in the main project like <ruleset>com/driftt/ruleset.xml</ruleset>.

     
  • Jim Madrigal

    Jim Madrigal - 2018-11-27

    I tried a few permutations of what you mentioned above but I'm not seeing any ruleset.xml in /target. The generated target winds up looking like this after a mvn clean install:

    classes
    generated-sources
    generated-test-sources
    maven-archiver
    surefire-reports
    test-classes
    CustomerService
    CustomerService-0.3-SNAPSHOT.jar
    CustomerService-0.3-SNAPSHOT-shaded.jar
    CustomerService-0.3-SNAPSHOT-sources.jar
    CustomerService-0.3-SNAPSHOT-test-sources.jar
    CustomerService-0.3-SNAPSHOT-tests.jar
    jacoco.exec
    

    Searching through all these subdirectories still doesn't turn up a ruleset.xml anywhere.

    the only mention of PMD in the output of the build is:

    [INFO] --- maven-pmd-plugin:3.11.0:pmd (default) @ CustomerService ---
    [INFO]
    [INFO] >>> maven-pmd-plugin:3.11.0:check (default) > :pmd @ CustomerService >>>
    [INFO]
    [INFO] --- maven-pmd-plugin:3.11.0:pmd (pmd) @ CustomerService ---
    [INFO]
    [INFO] <<< maven-pmd-plugin:3.11.0:check (default) < :pmd @ CustomerService <<<
    [INFO]
    [INFO]
    [INFO] --- maven-pmd-plugin:3.11.0:check (default) @ CustomerService ---
    
     

Log in to post a comment.

Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.