Create a plugin containing shared rules for using them in different projects.

Note
This tutorial is written for version 1.4.0 of jQAssistant.

1. Overview

jQAssistant allows sharing rules (i.e. concepts, constraints and groups) for being used by different projects. This way it is possible to…​

  • enforce coding standards and guidelines across organizations or teams by providing constraints

  • provide re-usable concepts for widely used frameworks to ease the creation of project specific constraints

The common approach to share rules is by distributing them as a jQAssistant plugin. For this tutorial two example constraints shall be made available:

maven:GroupId

Verifies that the groupId of all Maven modules within a project complies to a specific pattern.

package:RootPackage

Verifies that all Java classes of a project are located within a given root package. As the name of the package is project specific the constraints expects a parameter to be passed at build time.

For convenience reasons the rules are bundled into groups to ease execution and evolution.

2. Providing Rules As Plugin

2.1. The Plugin Structure

The standard approach of sharing rules is by distributing them as a jQAssistant plugin. Therefore a JAR artifact is created using a Maven project and the following pom.xml:

pom.xml
<?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>my.organization</groupId>
    <artifactId>rules</artifactId>
    <version>1.0.0-SNAPSHOT</version>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <!-- Test dependencies -->
        <dependency>
            <groupId>com.buschmais.jqassistant.core</groupId>
            <artifactId>store</artifactId>
            <version>1.4.0</version>
            <type>test-jar</type>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.buschmais.jqassistant.core</groupId>
            <artifactId>plugin</artifactId>
            <version>1.4.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.buschmais.jqassistant.plugin</groupId>
            <artifactId>common</artifactId>
            <type>test-jar</type>
            <version>1.4</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.buschmais.jqassistant.plugin</groupId>
            <artifactId>java</artifactId>
            <type>test-jar</type>
            <version>1.4</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.buschmais.jqassistant.plugin</groupId>
            <artifactId>java</artifactId>
            <version>1.4</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.buschmais.jqassistant.neo4jserver</groupId>
            <artifactId>neo4jv3</artifactId>
            <version>1.4.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.13</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

The file mainly declares test dependencies that will be used later for verifying the rules.

jQAssistant expects a plugin descriptor /META-INF/jqassistant-plugin.xml as classpath resource. It declares files containing rules that shall be shared:

src/main/resources/META-INF/jqassistant-plugin.xml
<jqa-plugin:jqassistant-plugin xmlns:jqa-plugin="http://www.buschmais.com/jqassistant/core/plugin/schema/v1.4" name="Shared Rules">
    <description>Provides shared rules for my organization.</description>
    <rules>
        <resource>maven.xml</resource>
        <resource>package.xml</resource>
        <resource>my-organization.xml</resource>
    </rules>
</jqa-plugin:jqassistant-plugin>

The referenced files are expected as classpath resources located in the folder /META-INF/jqassistant-rules. This tutorial uses XML documents to declare the rules. Thus the following folder structure is expected:

META-INF/
├── jqassistant-plugin.xml
└── jqassistant-rules/
    ├── jqassistant-plugin.xmlpackage.xml
    ├── jqassistant-plugin.xmlmaven.xml
    └── my-organization.xml
Note
Asciidoc files are supported as well but take longer time to load at startup. Therefore the recommended format is XML to not negatively affect the build process of other projects.

The next sections describe the shared rules in detail.

The file maven.xml provides rules related to Apache Maven:

src/main/resources/META-INF/jqassistant-rules/maven.xml
<jqa:jqassistant-rules xmlns:jqa="http://www.buschmais.com/jqassistant/core/rule/schema/v1.4">

    <group id="maven:Default">
        <includeConstraint refId="maven:GroupId"/>
    </group>

    <constraint id="maven:GroupId">
        <description><![CDATA[
            The groupId of all Maven projects must be "my.organization.<project name>"
            ]]>
        </description>
        <cypher><![CDATA[
            MATCH
              (project:Maven:Project)
            WHERE NOT
              project.groupId starts with "my.organization."
            RETURN
              project as Project, project.groupId as InvalidGroupId
        ]]></cypher>
    </constraint>

</jqa:jqassistant-rules>
maven:GroupId

A constraint verifying that each Maven project uses a defined pattern for the groupId.

maven:Default

A group that bundles selected constraints in the XML file. This approach allows for easier execution by just referencing this group and allows adding or removing rules in the future without the need to change the setup of projects using this plugin. There might be as well other groups including more or less rules, e.g. maven:Strict for more restrictive checks.

The file package.xml provides rules regarding the package structure of a project:

src/main/resources/META-INF/jqassistant-rules/package.xml
<jqa:jqassistant-rules xmlns:jqa="http://www.buschmais.com/jqassistant/core/rule/schema/v1.4">

    <group id="package:Default">
        <includeConstraint refId="package:RootPackage"/>
    </group>

    <constraint id="package:RootPackage">
        <requiresParameter name="rootPackage" type="String" />
        <description>All types of the application must be located within the root package or a sub-package of it.</description>
        <cypher><![CDATA[
            MATCH
              (package:Package)-[:CONTAINS]->(type:Type)
            WHERE NOT
              package.fqn starts with {rootPackage}
            RETURN
              type as TypeOutsideRootPackage
        ]]></cypher>
    </constraint>

</jqa:jqassistant-rules>
package:RootPackage

A constraint verifying that all Java types of a project are located within a defined root package. The name of the latter is expected as parameter rootPackage which must be present during execution of jQAssistant (e.g. as Maven plugin).

package:Default

A group with the same purpose as maven:Default described above.

Rules may be verified by tests. The following code snippet shows an example for the constraint package:RootPackage:

src/test/java/my/organization/rules/PackageTest.java
public class PackageTest extends AbstractJavaPluginIT {

    /**
     * Verifies that the constraint "package:RootPackage" is successful if all types
     * are located within the given root package.
     */
    @Test
    public void validRootPackage() throws IOException, RuleException {
        scanClassPathDirectory(getClassesDirectory(PackageTest.class));
        HashMap<String, String> parameters = new HashMap<>();
        parameters.put("rootPackage", PackageTest.class.getPackage().getName());

        assertThat(validateConstraint("package:RootPackage", parameters).getStatus(), equalTo(SUCCESS));
    }

    /**
     * Verifies that the constraint "package:RootPackage" is fails if any type is
     * located outside the given root package.
     */
    @Test
    public void invalidRootPackage() throws IOException, RuleException {
        scanClassPathDirectory(getClassesDirectory(PackageTest.class));
        HashMap<String, String> parameters = new HashMap<>();
        parameters.put("rootPackage", "org.jqassistant");

        Result<Constraint> result = validateConstraint("package:RootPackage", parameters);

        store.beginTransaction();
        assertThat(result.getStatus(), equalTo(FAILURE));
        List<Map<String, Object>> rows = result.getRows();
        assertThat(rows.size(), equalTo(1));
        Map<String, Object> row = rows.get(0);
        TypeDescriptor typeOutsideRootPackage = (TypeDescriptor) row.get("TypeOutsideRootPackage");
        assertThat(typeOutsideRootPackage, typeDescriptor(PackageTest.class));
        store.commitTransaction();
    }
}

2.4. Grouping Rules For Execution

The file my-organization.xml contains defines a group:

src/main/resources/META-INF/jqassistant-rules/my-organization.xml
<jqa:jqassistant-rules xmlns:jqa="http://www.buschmais.com/jqassistant/core/rule/schema/v1.4">

    <group id="my-organization:Default">
        <includeGroup refId="maven:Default"/>
        <includeGroup refId="package:Default"/>
    </group>

</jqa:jqassistant-rules>
my-organization:Default

This group bundles the groups maven:Default and package:Default. Thus a project using the rules provided by this plugin only needs to declare this group for execution.

Tip
Bundling rules into different groups can be used for staged roll-out into projects. New constraints at first can be included into a group my-organization:Staging and tested in selected projects (or using Maven profiles). After validation they can be moved to my-organization:Default (or groups included there) and thus become mandatory for all projects.

3. Using Shared Rules In A Maven Project

This section demonstrates how the set of shared rules may be activated in a Maven based project. The latter defines its own set of specific rules. These are bundled in the group project:Default which is declared in the file jqassistant/index.adoc

jqassistant/index.adoc
= Project

[[project:Default]]
[role=group]
== Default Project Rules

This section describes the default rules for this project.

// project specific rules to be added below or in other documents

The Maven build descriptor pom.xml contains the declaration for the jQAssistant plugin:

pom.xml
<plugin>
    <groupId>com.buschmais.jqassistant</groupId>
    <artifactId>jqassistant-maven-plugin</artifactId>
    <version>${jqassistant.version}</version>
    <executions>
        <execution>
            <id>default</id>
            <goals>
                <goal>scan</goal>
                <goal>analyze</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <groups>
            <!-- shared rules -->
            <group>my-organization:Default</group>
            <!-- project specific rules -->
            <group>project:Default</group>
        </groups>
        <ruleParameters>
            <!-- parameter required by the shared constraint "package:RootPackage" -->
            <rootPackage>my.organization</rootPackage>
        </ruleParameters>
    </configuration>
    <dependencies>
        <!-- plugin providing the shared rules -->
        <dependency>
            <groupId>my.organization</groupId>
            <artifactId>rules</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</plugin>
  • The plugin containing the shared rules is declared as dependency.

  • The configuration section selects the groups my-organization:Default and project:Default for execution.

  • Furthermore a value for the parameter rootPackage required by the constraint package:RootPackage is defined.

The project can now be built as usual and all required rules will be executed:

mvn install

4. Using Shared Rules With The Command Line Utility

For using shared rules the plugin JAR file must be copied to the folder plugins/ of the command line utility.

Furthermore a properties file must be created providing rule parameters, i.e. rootPackage required by the constraint package:RootPackage:

ruleParameters.properties
rootPackage=my.organization

Now the analysis can be executed using the following command:

# Unix
jqassistant.sh analyze -groups my-organization:Default,project:Default -ruleParameters ruleParameters.properties

# Windows
jqassistant.cmd analyze -groups my-organization:Default,project:Default -ruleParameters ruleParameters.properties

5. Resources