Create a plugin containing shared rules for using them in different projects.
Note
|
This tutorial is written for version 1.8.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
:
<?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.8.0</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.buschmais.jqassistant.core</groupId>
<artifactId>plugin</artifactId>
<version>1.8.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.buschmais.jqassistant.plugin</groupId>
<artifactId>common</artifactId>
<type>test-jar</type>
<version>1.8.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.buschmais.jqassistant.plugin</groupId>
<artifactId>java</artifactId>
<type>test-jar</type>
<version>1.8.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.buschmais.jqassistant.plugin</groupId>
<artifactId>java</artifactId>
<version>1.8.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.buschmais.jqassistant.neo4jserver</groupId>
<artifactId>neo4jv3</artifactId>
<version>1.8.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.5.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.14.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>java-hamcrest</artifactId>
<version>2.0.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.8.0-beta4</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:
<jqassistant-plugin xmlns="http://schema.jqassistant.org/plugin/v1.8"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://schema.jqassistant.org/plugin/v1.8 https://schema.jqassistant.org/plugin/jqassistant-plugin-v1.8.xsd"
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>
</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.
2.2. Maven Related Rules
The file maven.xml
provides rules related to Apache Maven:
<jqassistant-rules
xmlns="http://schema.jqassistant.org/rule/v1.8"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://schema.jqassistant.org/rule/v1.8 https://schema.jqassistant.org/rule/jqassistant-rule-v1.8.xsd">
<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>
</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.
2.3. Package Related Rules
The file package.xml
provides rules regarding the package structure of a project:
<jqassistant-rules
xmlns="http://schema.jqassistant.org/rule/v1.8"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://schema.jqassistant.org/rule/v1.8 https://schema.jqassistant.org/rule/jqassistant-rule-v1.8.xsd">
<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>
</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
:
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 RuleException {
scanClassPathDirectory(getClassesDirectory(PackageTest.class));
HashMap<String, String> parameters = new HashMap<>();
parameters.put("rootPackage", PackageTest.class.getPackage().getName());
assertThat(validateConstraint("package:RootPackage", parameters).getStatus()).isEqualTo(SUCCESS);
}
/**
* Verifies that the constraint "package:RootPackage" is fails if any type is
* located outside the given root package.
*/
@Test
public void invalidRootPackage() throws 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()).isEqualTo(FAILURE);
List<Map<String, Object>> rows = result.getRows();
assertThat(rows.size()).isEqualTo(1);
Map<String, Object> row = rows.get(0);
TypeDescriptor typeOutsideRootPackage = (TypeDescriptor) row.get("TypeOutsideRootPackage");
MatcherAssert.assertThat(typeOutsideRootPackage, typeDescriptor(PackageTest.class));
store.commitTransaction();
}
}
2.4. Grouping Rules For Execution
The file my-organization.xml
contains defines a group:
<jqassistant-rules
xmlns="http://schema.jqassistant.org/rule/v1.8"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://schema.jqassistant.org/rule/v1.8 https://schema.jqassistant.org/rule/jqassistant-rule-v1.8.xsd">
<group id="my-organization:Default">
<includeGroup refId="maven:Default"/>
<includeGroup refId="package:Default"/>
</group>
</jqassistant-rules>
- my-organization:Default
-
This group bundles the groups
maven:Default
andpackage: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
= 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:
<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
andproject:Default
for execution. -
Furthermore a value for the parameter
rootPackage
required by the constraintpackage: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
:
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