diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..18e5667
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,83 @@
+##############################
+## Java
+##############################
+.mtj.tmp/
+*.class
+*.jar
+*.war
+*.ear
+*.nar
+hs_err_pid*
+
+##############################
+## Maven
+##############################
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+pom.xml.bak
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+.mvn/wrapper/maven-wrapper.jar
+
+##############################
+## Gradle
+##############################
+bin/
+build/
+.gradle
+.gradletasknamecache
+gradle-app.setting
+!gradle-wrapper.jar
+
+##############################
+## IntelliJ
+##############################
+out/
+.idea/
+.idea_modules/
+*.iml
+*.ipr
+*.iws
+
+##############################
+## Eclipse
+##############################
+.settings/
+bin/
+tmp/
+.metadata
+.classpath
+.project
+*.tmp
+*.bak
+*.swp
+*~.nib
+local.properties
+.loadpath
+.factorypath
+
+##############################
+## NetBeans
+##############################
+nbproject/private/
+build/
+nbbuild/
+dist/
+nbdist/
+nbactions.xml
+nb-configuration.xml
+
+##############################
+## Visual Studio Code
+##############################
+.vscode/
+
+##############################
+## OS X
+##############################
+.DS_Store
diff --git a/README.md b/README.md
index d5d5ae0..f33e311 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,112 @@
-# mule-sonarqube-plugin
-Mule Sonarqube Plugin
+# Mule Sonarqube Validation Plugin
+
+Sonarqube plugin:
+
+* Ensure the consistency of Mule Applications by a set of predefined rules focused on specific quality attributes like: _Conceptual Integrity_, _Maintainability_, _Reusability_, _Security_, _Traceability_ and _Availability_.
+
+**This module requires Java 8**.
+
+## Prerequisites
+
+## Install mule-validation-xpath-core dependency
+
+1. Download the module source code.
+2. Open a terminal window and browse to module mule-validation-xpath-core folder.
+3. Install this module by running `mvn install`.
+
+## Plugin Generation
+
+1. Download the module source code.
+2. Open a terminal window and browse to module root folder.
+3. Build the mule plugin for Mule rules running `mvn clean package sonar-packaging:sonar-plugin -Dlanguage=mule`.
+
+## Maven Configuration
+1. In your settings file add sonar profile.
+ ```
+
+ sonar
+
+ true
+
+
+
+
+ http://localhost:9000
+
+
+
+ ```
+
+
+## Sonar Configuration
+1. Copy the generated file, mule-validation-sonarqube-plugin-{version}-mule.jar to *sonar-home*/extensions/plugins
+2. Copy rules [Mule 3 Rules](https://github.com/mulesoft-consulting/mule-validation-toolkit/blob/master/mule-validation-xpath-core/src/main/resources/rules-3.xml) or [Mule 4 Rules](https://github.com/mulesoft-consulting/mule-validation-toolkit/blob/master/mule-validation-xpath-core/src/main/resources/rules-4.xml) to *sonar-home*/extensions/plugins
+3. Start the server
+
+## Project Configuration
+### Alternative 1
+1. Modify project's pom.xml file to add language and location of the source files to analysis.
+```
+
+ ...
+ src
+ ...
+
+```
+2. Analyze the project executing mvn sonar:sonar
+
+### Alternative 2
+1. Analyze the project executing mvn sonar:sonar -Dsonar.sources=src
+
+## Release Notes
+
+#### 1.0.0
+##### Changes
+ - Added Line Number processing in Coverage Sensor
+
+#### 0.0.11
+##### Changes
+ - Added Rule Template.
+ You could create new custom rules from SonarQube Console.
+
+#### 0.0.10
+##### Changes
+ - Added Rule description.
+ Now you could add an extended description to the rules. The description supports HTML.
+
+#### 0.0.9
+##### Changes
+ - Added property sonar.mule.ruleset.categories. It allows to filter ruleset categories to apply in the project.
+ Value should be a string separated by commas.
+ For example run, `mvn sonar:sonar -Dsonar.mule.ruleset.categories=flows` to only apply the flows category ruleset
+
+#### 0.0.8
+##### Changes
+ - Bug Fixes
+ - Added Metric LOC
+ - Added Support for SonarQube 7.2.1
+#### 0.0.7
+##### Changes
+ - Added new rules + http namespace
+#### 0.0.6
+##### Changes
+ - Added Munit Coverage and Minor Improvements
+#### 0.0.5
+##### Changes
+ - Externalized Rules to $SONARQUBE_HOME/extensions/plugins/[rules-3.xml|rules-4.xml].
+ Basic set of rules for [mule3](https://github.com/mulesoft-consulting/mule-validation-toolkit/blob/master/mule-validation-xpath-core/src/main/resources/rules-3.xml), [mule4](https://github.com/mulesoft-consulting/mule-validation-toolkit/blob/master/mule-validation-xpath-core/src/main/resources/rules-4.xml)
+
+ **Adding new rules: if it is needed to add a new namespace you will have to added to [mule3-namespaces](https://github.com/mulesoft-consulting/mule-validation-toolkit/blob/master/mule-validation-xpath-core/src/main/resources/namespace-3.properties) or to [mule4-namespaces](https://github.com/mulesoft-consulting/mule-validation-toolkit/blob/master/mule-validation-xpath-core/src/main/resources/namespace-4.properties) and regenerate all plugins**
+
+ **Updating existing rule: if you need to update an existing rule, you will also have to update its id number**
+
+#### 0.0.4
+##### Changes
+ - Added Measures
+ - Number of Flows
+ - Number of SubFlows
+ - Number of DW Transformations
+ - Application Complexity
+
+## Final Note
+Enjoy and provide feedback / contribute :)
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..4ac51b5
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,82 @@
+
+ 4.0.0
+
+ com.mulesoft.services
+ mule-validation-sonarqube-plugin
+ 1.0.0
+ jar
+
+ http://maven.apache.org
+ Sonar Plugin for Mule Projects
+
+
+
+ UTF-8
+ 7.7
+ java
+
+
+
+
+ junit
+ junit
+ 3.8.1
+ test
+
+
+ org.sonarsource.sonarqube
+ sonar-plugin-api
+
+ ${sonar.apiVersion}
+
+ provided
+
+
+ org.jdom
+ jdom2
+ 2.0.6
+
+
+ com.mulesoft.services
+ mule-validation-xpath-core
+ 0.1.0
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.8.9
+
+
+
+ ${project.artifactId}-${project.version}-mule
+
+
+ org.sonarsource.sonar-packaging-maven-plugin
+ sonar-packaging-maven-plugin
+ 1.17
+ true
+
+
+ com.mulesoft.services.tools.sonarqube.MulePlugin
+ MulePlugin
+ mulevalidationsonarqubeplugin-mule
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.7.0
+
+
+ 1.8
+ UTF-8
+
+
+
+
+
diff --git a/src/main/java/com/mulesoft/services/tools/sonarqube/MulePlugin.java b/src/main/java/com/mulesoft/services/tools/sonarqube/MulePlugin.java
new file mode 100644
index 0000000..e7acadd
--- /dev/null
+++ b/src/main/java/com/mulesoft/services/tools/sonarqube/MulePlugin.java
@@ -0,0 +1,54 @@
+package com.mulesoft.services.tools.sonarqube;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.Plugin;
+import org.sonar.api.config.PropertyDefinition;
+import org.sonar.api.resources.Qualifiers;
+
+import com.mulesoft.services.tools.sonarqube.language.MuleLanguage;
+import com.mulesoft.services.tools.sonarqube.measures.MuleFlowCount;
+import com.mulesoft.services.tools.sonarqube.measures.MuleSizeRating;
+import com.mulesoft.services.tools.sonarqube.measures.MuleSubFlowCount;
+import com.mulesoft.services.tools.sonarqube.measures.MuleTransformationCount;
+import com.mulesoft.services.tools.sonarqube.metrics.ConfigurationFilesSensor;
+import com.mulesoft.services.tools.sonarqube.metrics.CoverageSensor;
+import com.mulesoft.services.tools.sonarqube.metrics.MUnitSensor;
+import com.mulesoft.services.tools.sonarqube.metrics.MuleMetrics;
+import com.mulesoft.services.tools.sonarqube.profile.MuleQualityProfile;
+import com.mulesoft.services.tools.sonarqube.rule.MuleRulesDefinition;
+import com.mulesoft.services.tools.sonarqube.sensor.MuleSensor;
+
+public class MulePlugin implements Plugin {
+
+ // public static final String LANGUAGE_NAME = "Mule";
+ private static final String GENERAL = "General";
+
+ Logger logger = LoggerFactory.getLogger(getClass());
+
+ @Override
+ public void define(Context context) {
+ if (logger.isDebugEnabled())
+ logger.debug("Configuring Mule Plugin");
+
+ // Added Language
+ context.addExtensions(MuleLanguage.class, MuleSensor.class);
+ // context.addExtension(getProperties());
+
+ // Added Rules
+ context.addExtension(MuleRulesDefinition.class);
+
+ // Added Profile
+ context.addExtension(MuleQualityProfile.class);
+
+ context.addExtension(PropertyDefinition.builder(MuleLanguage.FILE_SUFFIXES_KEY)
+ .defaultValue(MuleLanguage.FILE_SUFFIXES_DEFAULT_VALUE).name("File Suffixes")
+ .description("List of suffixes for files to analyze.").subCategory(GENERAL)
+ .category(MuleLanguage.LANGUAGE_NAME).multiValues(true).onQualifiers(Qualifiers.PROJECT).build());
+
+ context.addExtensions(MuleMetrics.class, ConfigurationFilesSensor.class, MuleSizeRating.class,
+ MuleFlowCount.class, MuleSubFlowCount.class, MuleTransformationCount.class, CoverageSensor.class,
+ MUnitSensor.class);
+ }
+
+}
diff --git a/src/main/java/com/mulesoft/services/tools/sonarqube/filter/MuleFilePredicate.java b/src/main/java/com/mulesoft/services/tools/sonarqube/filter/MuleFilePredicate.java
new file mode 100644
index 0000000..80a58dc
--- /dev/null
+++ b/src/main/java/com/mulesoft/services/tools/sonarqube/filter/MuleFilePredicate.java
@@ -0,0 +1,39 @@
+package com.mulesoft.services.tools.sonarqube.filter;
+
+import java.io.IOException;
+
+import org.jdom2.Document;
+import org.jdom2.JDOMException;
+import org.jdom2.input.SAXBuilder;
+import org.sonar.api.batch.fs.FilePredicate;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+public class MuleFilePredicate implements FilePredicate {
+ private final Logger logger = Loggers.get(MuleFilePredicate.class);
+ SAXBuilder saxBuilder = new SAXBuilder();
+ String muleNamespace = "http://www.mulesoft.org/schema/mule/core";
+ String fileExtension = ".xml";
+
+ @Override
+ public boolean apply(InputFile inputFile) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Executing Mule Sensor on file:" + inputFile.filename());
+ }
+
+ if (inputFile.filename().endsWith(fileExtension)) {
+ try {
+ Document document = saxBuilder.build(inputFile.inputStream());
+
+ String namespace = document.getRootElement().getNamespaceURI();
+ if (muleNamespace.equals(namespace))
+ return true;
+ } catch (JDOMException | IOException e) {
+ logger.error("Parsing document:" + inputFile.filename(), e);
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/src/main/java/com/mulesoft/services/tools/sonarqube/language/MuleLanguage.java b/src/main/java/com/mulesoft/services/tools/sonarqube/language/MuleLanguage.java
new file mode 100644
index 0000000..f7e5f36
--- /dev/null
+++ b/src/main/java/com/mulesoft/services/tools/sonarqube/language/MuleLanguage.java
@@ -0,0 +1,41 @@
+package com.mulesoft.services.tools.sonarqube.language;
+
+import java.util.Arrays;
+
+import org.sonar.api.config.Configuration;
+import org.sonar.api.resources.AbstractLanguage;
+
+/**
+ * Mule Language Definition
+ *
+ * @author franco.parma
+ *
+ */
+public class MuleLanguage extends AbstractLanguage {
+
+ protected Configuration config = null;
+
+ public static final String LANGUAGE_NAME = "Mule";
+ public static final String LANGUAGE_KEY = "mule";
+
+ public static final String LANGUAGE_MULE4_KEY = "mule4";
+ public static final String LANGUAGE_MULE3_KEY = "mule3";
+
+ public static final String FILE_SUFFIXES_KEY = "sonar.mule.file.suffixes";
+ public static final String FILE_SUFFIXES_DEFAULT_VALUE = ".xml";
+
+ public MuleLanguage(Configuration config) {
+ super("mule", LANGUAGE_NAME);
+ this.config = config;
+ }
+
+ @Override
+ public String[] getFileSuffixes() {
+ String[] suffixes = config.getStringArray(FILE_SUFFIXES_KEY);
+ if (suffixes.length == 0) {
+ suffixes = Arrays.asList(FILE_SUFFIXES_DEFAULT_VALUE.split(",")).toArray(suffixes);
+ }
+ return suffixes;
+ }
+
+}
diff --git a/src/main/java/com/mulesoft/services/tools/sonarqube/measures/MuleFlowCount.java b/src/main/java/com/mulesoft/services/tools/sonarqube/measures/MuleFlowCount.java
new file mode 100644
index 0000000..67a9b88
--- /dev/null
+++ b/src/main/java/com/mulesoft/services/tools/sonarqube/measures/MuleFlowCount.java
@@ -0,0 +1,39 @@
+package com.mulesoft.services.tools.sonarqube.measures;
+
+import org.sonar.api.ce.measure.Component;
+import org.sonar.api.ce.measure.Measure;
+import org.sonar.api.ce.measure.MeasureComputer;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+import com.mulesoft.services.tools.sonarqube.metrics.MuleMetrics;
+
+/**
+ * Computes the total number of Flows
+ * @author franco.perez
+ *
+ */
+public class MuleFlowCount implements MeasureComputer {
+
+ private final Logger logger = Loggers.get(this.getClass());
+
+ @Override
+ public MeasureComputerDefinition define(MeasureComputerDefinitionContext defContext) {
+ return defContext.newDefinitionBuilder().setOutputMetrics(MuleMetrics.FLOWS.key()).build();
+ }
+
+ @Override
+ public void compute(MeasureComputerContext context) {
+ logger.info("Computing Mule Flow Size");
+
+ if (context.getComponent().getType() != Component.Type.FILE) {
+ int sumFlows = 0;
+ for (Measure child : context.getChildrenMeasures(MuleMetrics.FLOWS.key())) {
+ sumFlows += child.getIntValue();
+ }
+ context.addMeasure(MuleMetrics.FLOWS.key(), sumFlows);
+
+ }
+ }
+
+}
diff --git a/src/main/java/com/mulesoft/services/tools/sonarqube/measures/MuleSizeRating.java b/src/main/java/com/mulesoft/services/tools/sonarqube/measures/MuleSizeRating.java
new file mode 100644
index 0000000..361d135
--- /dev/null
+++ b/src/main/java/com/mulesoft/services/tools/sonarqube/measures/MuleSizeRating.java
@@ -0,0 +1,53 @@
+package com.mulesoft.services.tools.sonarqube.measures;
+
+import org.sonar.api.ce.measure.Measure;
+import org.sonar.api.ce.measure.MeasureComputer;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+import com.mulesoft.services.tools.sonarqube.metrics.MuleMetrics;
+
+/**
+ * Computes the complex rating depending on the number of flows
+ * @author franco.perez
+ *
+ */
+public class MuleSizeRating implements MeasureComputer {
+
+ private final Logger logger = Loggers.get(this.getClass());
+ private static final int THRESHOLD_SIMPLE = 7;
+ private static final int THRESHOLD_MEDIUM = 15;
+ private static final int RATING_A = 1;
+ private static final int RATING_B = 2;
+ private static final int RATING_C = 3;
+
+ @Override
+ public MeasureComputerDefinition define(MeasureComputerDefinitionContext def) {
+ return def.newDefinitionBuilder().setInputMetrics(MuleMetrics.FLOWS.key(), MuleMetrics.SUBFLOWS.key())
+ .setOutputMetrics(MuleMetrics.CONFIGURATION_FILES_COMP_RATING.key()).build();
+ }
+
+ @Override
+ public void compute(MeasureComputerContext context) {
+ logger.info("Computing MuleSizeRating");
+ Measure flows = context.getMeasure(MuleMetrics.FLOWS.key());
+ Measure subflows = context.getMeasure(MuleMetrics.SUBFLOWS.key());
+ int totalNumberOfFlows = 0;
+ if (flows != null) {
+ totalNumberOfFlows += flows.getIntValue();
+ }
+ if (subflows != null) {
+ totalNumberOfFlows += subflows.getIntValue();
+ }
+
+ // rating values are currently implemented as integers in API
+ int rating = RATING_A;
+ if (totalNumberOfFlows > THRESHOLD_SIMPLE) {
+ rating = RATING_B;
+ }
+ if (totalNumberOfFlows > THRESHOLD_MEDIUM) {
+ rating = RATING_C;
+ }
+ context.addMeasure(MuleMetrics.CONFIGURATION_FILES_COMP_RATING.key(), rating);
+ }
+}
diff --git a/src/main/java/com/mulesoft/services/tools/sonarqube/measures/MuleSubFlowCount.java b/src/main/java/com/mulesoft/services/tools/sonarqube/measures/MuleSubFlowCount.java
new file mode 100644
index 0000000..c8528a3
--- /dev/null
+++ b/src/main/java/com/mulesoft/services/tools/sonarqube/measures/MuleSubFlowCount.java
@@ -0,0 +1,37 @@
+package com.mulesoft.services.tools.sonarqube.measures;
+
+import org.sonar.api.ce.measure.Component;
+import org.sonar.api.ce.measure.Measure;
+import org.sonar.api.ce.measure.MeasureComputer;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+import com.mulesoft.services.tools.sonarqube.metrics.MuleMetrics;
+
+/**
+ * Computes the total number of Subflows
+ * @author franco.perez
+ *
+ */
+public class MuleSubFlowCount implements MeasureComputer {
+
+ private final Logger logger = Loggers.get(this.getClass());
+
+ @Override
+ public MeasureComputerDefinition define(MeasureComputerDefinitionContext defContext) {
+ return defContext.newDefinitionBuilder().setOutputMetrics(MuleMetrics.SUBFLOWS.key()).build();
+ }
+
+ @Override
+ public void compute(MeasureComputerContext context) {
+ logger.info("Computing Mule SubFlow Size");
+
+ if (context.getComponent().getType() != Component.Type.FILE) {
+ int sumFlows = 0;
+ for (Measure child : context.getChildrenMeasures(MuleMetrics.SUBFLOWS.key())) {
+ sumFlows += child.getIntValue();
+ }
+ context.addMeasure(MuleMetrics.SUBFLOWS.key(), sumFlows);
+ }
+ }
+}
diff --git a/src/main/java/com/mulesoft/services/tools/sonarqube/measures/MuleTransformationCount.java b/src/main/java/com/mulesoft/services/tools/sonarqube/measures/MuleTransformationCount.java
new file mode 100644
index 0000000..79b81e7
--- /dev/null
+++ b/src/main/java/com/mulesoft/services/tools/sonarqube/measures/MuleTransformationCount.java
@@ -0,0 +1,39 @@
+package com.mulesoft.services.tools.sonarqube.measures;
+
+import org.sonar.api.ce.measure.Component;
+import org.sonar.api.ce.measure.Measure;
+import org.sonar.api.ce.measure.MeasureComputer;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+import com.mulesoft.services.tools.sonarqube.metrics.MuleMetrics;
+
+/**
+ * Computes the total number of transformations
+ * @author franco.perez
+ *
+ */
+public class MuleTransformationCount implements MeasureComputer {
+ private final Logger logger = Loggers.get(this.getClass());
+
+ @Override
+ public MeasureComputerDefinition define(MeasureComputerDefinitionContext defContext) {
+ return defContext.newDefinitionBuilder().setOutputMetrics(MuleMetrics.TRANSFORMATIONS.key()).build();
+ }
+
+ @Override
+ public void compute(MeasureComputerContext context) {
+ logger.info("Computing Mule Transformation Count");
+
+ if (context.getComponent().getType() != Component.Type.FILE) {
+ int sumFlows = 0;
+ for (Measure child : context.getChildrenMeasures(MuleMetrics.TRANSFORMATIONS.key())) {
+ sumFlows += child.getIntValue();
+ }
+ context.addMeasure(MuleMetrics.TRANSFORMATIONS.key(), sumFlows);
+
+ }
+
+ }
+
+}
diff --git a/src/main/java/com/mulesoft/services/tools/sonarqube/metrics/AbstractMuleSensor.java b/src/main/java/com/mulesoft/services/tools/sonarqube/metrics/AbstractMuleSensor.java
new file mode 100644
index 0000000..6162c83
--- /dev/null
+++ b/src/main/java/com/mulesoft/services/tools/sonarqube/metrics/AbstractMuleSensor.java
@@ -0,0 +1,29 @@
+package com.mulesoft.services.tools.sonarqube.metrics;
+
+import org.jdom2.input.SAXBuilder;
+import org.sonar.api.batch.fs.FileSystem;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.sensor.Sensor;
+import org.sonar.api.batch.sensor.SensorContext;
+
+import com.mulesoft.services.tools.sonarqube.filter.MuleFilePredicate;
+import com.mulesoft.services.tools.sonarqube.sensor.MuleSensor;
+
+public abstract class AbstractMuleSensor implements Sensor {
+
+ SAXBuilder saxBuilder = new SAXBuilder();
+
+ @Override
+ public void execute(SensorContext context) {
+
+ FileSystem fs = context.fileSystem();
+ // Only ConfigurationFiles
+ Iterable files = fs.inputFiles(new MuleFilePredicate());
+ for (InputFile file : files) {
+ process(context, file, MuleSensor.getLanguage(context));
+ }
+ }
+
+ protected abstract void process(SensorContext context, InputFile file, String language);
+
+}
diff --git a/src/main/java/com/mulesoft/services/tools/sonarqube/metrics/ConfigurationFilesSensor.java b/src/main/java/com/mulesoft/services/tools/sonarqube/metrics/ConfigurationFilesSensor.java
new file mode 100644
index 0000000..3439b98
--- /dev/null
+++ b/src/main/java/com/mulesoft/services/tools/sonarqube/metrics/ConfigurationFilesSensor.java
@@ -0,0 +1,78 @@
+package com.mulesoft.services.tools.sonarqube.metrics;
+
+import java.io.IOException;
+
+import org.jdom2.Document;
+import org.jdom2.Element;
+import org.jdom2.JDOMException;
+import org.jdom2.input.SAXBuilder;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.measure.Metric;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.SensorDescriptor;
+import org.sonar.api.batch.sensor.measure.NewMeasure;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+import com.mulesoft.services.tools.sonarqube.language.MuleLanguage;
+import com.mulesoft.services.tools.sonarqube.properties.MuleProperties;
+import com.mulesoft.services.xpath.XPathProcessor;
+
+public class ConfigurationFilesSensor extends AbstractMuleSensor {
+
+ private final Logger logger = Loggers.get(ConfigurationFilesSensor.class);
+ SAXBuilder saxBuilder = new SAXBuilder();
+
+ private static final String METRIC_FLOW_PROPERTY = "mule.metric.flow";
+ private static final String METRIC_SUBFLOW_PROPERTY = "mule.metric.subflow";
+ private static final String METRIC_DW_PAYLOAD_PROPERTY = "mule.metric.dw.payload";
+ private static final String METRIC_DW_VARIABLE_PROPERTY = "mule.metric.dw.variable";
+
+ @Override
+ public void describe(SensorDescriptor descriptor) {
+ descriptor.name("Compute size of configuration file");
+ }
+
+ @Override
+ protected void process(SensorContext context, InputFile file, String language) {
+ try {
+ Document document = saxBuilder.build(file.inputStream());
+ Element rootElement = document.getRootElement();
+ XPathProcessor xpathProcessor = new XPathProcessor()
+ .loadNamespaces(language.equals(MuleLanguage.LANGUAGE_MULE4_KEY) ? "namespace-4.properties"
+ : "namespace-3.properties");
+
+ saveMetric(xpathProcessor, MuleMetrics.FLOWS, context, file, rootElement,
+ MuleProperties.getProperties(language).get(METRIC_FLOW_PROPERTY).toString());
+
+ saveMetric(xpathProcessor, MuleMetrics.SUBFLOWS, context, file, rootElement,
+ MuleProperties.getProperties(language).get(METRIC_SUBFLOW_PROPERTY).toString());
+
+ // Lines of code = Lines in Mule
+ saveMetric(file.lines(), CoreMetrics.NCLOC, context, file);
+
+ saveMetric(xpathProcessor, MuleMetrics.TRANSFORMATIONS, context, file, rootElement,
+ MuleProperties.getProperties(language).get(METRIC_DW_PAYLOAD_PROPERTY).toString(),
+ MuleProperties.getProperties(language).get(METRIC_DW_VARIABLE_PROPERTY).toString());
+
+ } catch (JDOMException | IOException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+ private void saveMetric(XPathProcessor helper, Metric metric, SensorContext context, InputFile file,
+ Element rootElement, String... xpathExpressions) {
+ int result = 0;
+ for (int i = 0; i < xpathExpressions.length; i++) {
+ result += helper.processXPath(xpathExpressions[i], rootElement, Double.class).intValue();
+ }
+ saveMetric(result, metric, context, file);
+ }
+
+ private void saveMetric(int result, Metric metric, SensorContext context, InputFile file) {
+ NewMeasure metrics = context.newMeasure();
+ metrics.forMetric(metric).on(file).withValue(result).save();
+ }
+
+}
diff --git a/src/main/java/com/mulesoft/services/tools/sonarqube/metrics/CoverageSensor.java b/src/main/java/com/mulesoft/services/tools/sonarqube/metrics/CoverageSensor.java
new file mode 100644
index 0000000..9cdcafc
--- /dev/null
+++ b/src/main/java/com/mulesoft/services/tools/sonarqube/metrics/CoverageSensor.java
@@ -0,0 +1,155 @@
+package com.mulesoft.services.tools.sonarqube.metrics;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Scanner;
+
+import org.sonar.api.batch.fs.FileSystem;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.sensor.Sensor;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.SensorDescriptor;
+import org.sonar.api.batch.sensor.coverage.NewCoverage;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.mulesoft.services.tools.sonarqube.filter.MuleFilePredicate;
+import com.mulesoft.services.tools.sonarqube.language.MuleLanguage;
+import com.mulesoft.services.tools.sonarqube.properties.MuleProperties;
+import com.mulesoft.services.tools.sonarqube.sensor.MuleSensor;
+
+public class CoverageSensor implements Sensor {
+
+ private final Logger logger = Loggers.get(CoverageSensor.class);
+ private static final String MUNIT_NAME_PROPERTY = "mule.munit.properties.name";
+ private static final String MUNIT_FLOWS_PROPERTY = "mule.munit.properties.flows";
+ private static final String MUNIT_FILES_PROPERTY = "mule.munit.properties.files";
+ private static final String MUNIT_LINES_PROPERTY = "mule.munit.properties.lines";
+ private static final String MUNIT_COVERAGE_PROPERTY = "mule.munit.properties.coverage";
+ private static final String MUNIT_PROCESSOR_COUNT = "mule.munit.properties.processorCount";
+ private static final String MUNIT_COVERED_PROCESSOR_COUNT = "mule.munit.properties.coveredProcessorCount";
+ private static final String MUNIT_LINE_NUMBER = "mule.munit.properties.lineNumber";
+ private static final String MUNIT_COVERED = "mule.munit.properties.covered";
+
+ ObjectMapper objectMapper = new ObjectMapper();
+
+ private static final String MUNIT_REPORT_MULE_3 = java.io.File.separator + "target" + java.io.File.separator
+ + "munit-reports" + java.io.File.separator + "coverage-json" + java.io.File.separator + "report.json";
+ private static final String MUNIT_REPORT_MULE_4 = java.io.File.separator + "target" + java.io.File.separator
+ + "site" + java.io.File.separator + "munit" + java.io.File.separator + "coverage" + java.io.File.separator
+ + "munit-coverage.json";
+
+ @Override
+ public void describe(SensorDescriptor descriptor) {
+ descriptor.name("Compute the coverage of the applications");
+ descriptor.onlyOnLanguage(MuleLanguage.LANGUAGE_KEY);
+ }
+
+ @Override
+ public void execute(SensorContext context) {
+ File munitJsonReport = new File(context.fileSystem().baseDir()
+ + (MuleSensor.getLanguage(context).equals(MuleLanguage.LANGUAGE_MULE4_KEY) ? MUNIT_REPORT_MULE_4
+ : MUNIT_REPORT_MULE_3));
+ if (munitJsonReport.exists()) {
+ Map coverage = loadResults(
+ MuleProperties.getProperties(MuleSensor.getLanguage(context)), munitJsonReport);
+ FileSystem fs = context.fileSystem();
+ // Only ConfigurationFiles
+ Iterable files = fs.inputFiles(new MuleFilePredicate());
+ for (InputFile file : files) {
+ saveCoverage(coverage, file.filename(), context, file);
+ }
+ }
+ }
+
+ private Map loadResults(Properties props, File munitJsonReport) {
+ Map coverageMap = null;
+ try (Scanner scanner = new Scanner(munitJsonReport)) {
+
+ String jsonInput = scanner.useDelimiter("\\Z").next();
+ JsonNode root = objectMapper.readTree(jsonInput);
+ String coverage = root.get(props.getProperty(MUNIT_COVERAGE_PROPERTY)).asText();
+ if (logger.isDebugEnabled()) {
+ logger.debug("Total Coverage :" + coverage);
+ }
+ ArrayNode files = (ArrayNode) root.get(props.getProperty(MUNIT_FILES_PROPERTY));
+ coverageMap = new HashMap();
+ for (Iterator iterator = files.iterator(); iterator.hasNext();) {
+ JsonNode node = iterator.next();
+ String name = node.get(props.getProperty(MUNIT_NAME_PROPERTY)).asText();
+ FlowCoverageCounter counter = new FlowCoverageCounter();
+ ArrayNode flows = (ArrayNode) node.get(props.getProperty(MUNIT_FLOWS_PROPERTY));
+ for (Iterator flowsIterator = flows.iterator(); flowsIterator.hasNext();) {
+ JsonNode flowNode = flowsIterator.next();
+ int messageProcessorCount = flowNode.get(props.getProperty(MUNIT_PROCESSOR_COUNT)).asInt();
+ int coveredProcessorCount = flowNode.get(props.getProperty(MUNIT_COVERED_PROCESSOR_COUNT)).asInt();
+ counter.addProcessors(messageProcessorCount);
+ counter.addCoveredProcessors(coveredProcessorCount);
+ ArrayNode lines = (ArrayNode) flowNode.get(props.getProperty(MUNIT_LINES_PROPERTY));
+ if (lines != null) {
+ counter.setHasLineNumbers(true);
+ for (Iterator linesIterator = lines.iterator(); linesIterator.hasNext();) {
+ JsonNode linesNode = linesIterator.next();
+ int lineNumber = linesNode.get(props.getProperty(MUNIT_LINE_NUMBER)).asInt();
+ boolean covered = linesNode.get(props.getProperty(MUNIT_COVERED)).asBoolean();
+
+ if (covered) {
+ counter.getCoveredLines().add(lineNumber);
+ } else {
+ counter.getNotcoveredLines().add(lineNumber);
+ }
+ }
+ }
+ }
+ String[] fileParts = name.split(File.separator);
+ coverageMap.put(fileParts[fileParts.length - 1], counter);
+ if (logger.isDebugEnabled()) {
+ logger.debug("name :" + node.get("name") + " : coverage:" + node.get("coverage"));
+ }
+ }
+ } catch (IOException e) {
+ logger.error(e.getMessage(), e);
+ }
+ return coverageMap;
+
+ }
+
+ private void saveCoverage(Map coverageMap, String fileName, SensorContext context,
+ InputFile file) {
+
+ FlowCoverageCounter coverage = coverageMap.get(fileName);
+ if (coverage != null) {
+ NewCoverage newCoverage = context.newCoverage().onFile(file);
+
+ if (coverage.hasLineNumbers()) {
+ for (Iterator iterator = coverage.getCoveredLines().iterator(); iterator.hasNext();) {
+ Integer lineNumber = iterator.next();
+ newCoverage.lineHits(lineNumber, 1);
+ }
+ for (Iterator iterator = coverage.getNotcoveredLines().iterator(); iterator.hasNext();) {
+ Integer lineNumber = iterator.next();
+ newCoverage.lineHits(lineNumber, 0);
+ }
+ newCoverage.save();
+ } else {
+ int processors = coverage.getProcessors();
+ int covered = coverage.getCoveredProcessors();
+
+ for (int i = 0; i < processors; i++) {
+ newCoverage.lineHits(i + 1, covered > 0 ? 1 : 0);
+ covered--;
+ }
+
+ newCoverage.save();
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/com/mulesoft/services/tools/sonarqube/metrics/FlowCoverageCounter.java b/src/main/java/com/mulesoft/services/tools/sonarqube/metrics/FlowCoverageCounter.java
new file mode 100644
index 0000000..b613ef2
--- /dev/null
+++ b/src/main/java/com/mulesoft/services/tools/sonarqube/metrics/FlowCoverageCounter.java
@@ -0,0 +1,52 @@
+package com.mulesoft.services.tools.sonarqube.metrics;
+
+import java.util.ArrayList;
+
+public class FlowCoverageCounter {
+
+ private int processors = 0;
+ private int coveredProcessors = 0;
+ private boolean hasLineNumbers = false;
+ private ArrayList coveredLines = new ArrayList();
+ private ArrayList notcoveredLines = new ArrayList();
+
+ public void addProcessors(int processors) {
+ this.processors += processors;
+ }
+
+ public void addCoveredProcessors(int processors) {
+ this.coveredProcessors += processors;
+ }
+
+ public int getCoveredProcessors() {
+ return coveredProcessors;
+ }
+
+ public int getProcessors() {
+ return processors;
+ }
+
+ public void setCoveredLines(ArrayList coveredLines) {
+ this.coveredLines = coveredLines;
+ }
+
+ public ArrayList getCoveredLines() {
+ return coveredLines;
+ }
+
+ public ArrayList getNotcoveredLines() {
+ return notcoveredLines;
+ }
+
+ public void setNotcoveredLines(ArrayList notcoveredLines) {
+ this.notcoveredLines = notcoveredLines;
+ }
+
+ public void setHasLineNumbers(boolean hasLineNumbers) {
+ this.hasLineNumbers = hasLineNumbers;
+ }
+
+ public boolean hasLineNumbers() {
+ return hasLineNumbers;
+ }
+}
diff --git a/src/main/java/com/mulesoft/services/tools/sonarqube/metrics/MUnitSensor.java b/src/main/java/com/mulesoft/services/tools/sonarqube/metrics/MUnitSensor.java
new file mode 100644
index 0000000..3858648
--- /dev/null
+++ b/src/main/java/com/mulesoft/services/tools/sonarqube/metrics/MUnitSensor.java
@@ -0,0 +1,58 @@
+package com.mulesoft.services.tools.sonarqube.metrics;
+
+import java.io.IOException;
+
+import org.jdom2.Document;
+import org.jdom2.Element;
+import org.jdom2.JDOMException;
+import org.jdom2.input.SAXBuilder;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.measure.Metric;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.SensorDescriptor;
+import org.sonar.api.batch.sensor.measure.NewMeasure;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+import com.mulesoft.services.tools.sonarqube.language.MuleLanguage;
+import com.mulesoft.services.xpath.XPathProcessor;
+
+public class MUnitSensor extends AbstractMuleSensor {
+
+ private final Logger logger = Loggers.get(MUnitSensor.class);
+
+ SAXBuilder saxBuilder = new SAXBuilder();
+
+ private static final String UNIT_TESTS_XPATH_EXPRESSION = "count(//mule:mule/munit:test)";
+
+ @Override
+ public void describe(SensorDescriptor descriptor) {
+ descriptor.name("Compute number of unit test cases");
+ }
+
+ @Override
+ protected void process(SensorContext context, InputFile file, String language) {
+ try {
+ Document document = saxBuilder.build(file.inputStream());
+ Element rootElement = document.getRootElement();
+ XPathProcessor xpathProcessor = new XPathProcessor()
+ .loadNamespaces(language.equals(MuleLanguage.LANGUAGE_MULE4_KEY) ? "namespace-4.properties"
+ : "namespace-3.properties");
+ saveMetric(xpathProcessor, CoreMetrics.TESTS, context, file, rootElement, UNIT_TESTS_XPATH_EXPRESSION);
+ } catch (JDOMException | IOException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+ private void saveMetric(XPathProcessor helper, Metric metric, SensorContext context, InputFile file,
+ Element rootElement, String... xpathExpressions) {
+ int result = 0;
+ for (int i = 0; i < xpathExpressions.length; i++) {
+ result += helper.processXPath(xpathExpressions[i], rootElement, Double.class).intValue();
+ }
+ NewMeasure metrics = context.newMeasure();
+ metrics.forMetric(metric).on(file).withValue(result).save();
+ }
+
+}
diff --git a/src/main/java/com/mulesoft/services/tools/sonarqube/metrics/MuleMetrics.java b/src/main/java/com/mulesoft/services/tools/sonarqube/metrics/MuleMetrics.java
new file mode 100644
index 0000000..26de73e
--- /dev/null
+++ b/src/main/java/com/mulesoft/services/tools/sonarqube/metrics/MuleMetrics.java
@@ -0,0 +1,41 @@
+package com.mulesoft.services.tools.sonarqube.metrics;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.measures.Metrics;
+
+public class MuleMetrics implements Metrics {
+
+ public static final Metric FLOWS = new Metric.Builder("mule_flows", "Flows", Metric.ValueType.INT)
+ .setDescription("Number of flows").setDirection(Metric.DIRECTION_WORST).setQualitative(false)
+ .setDomain(org.sonar.api.measures.CoreMetrics.DOMAIN_SIZE).create();
+
+ public static final Metric SUBFLOWS = new Metric.Builder("mule_subflows", "SubFlows", Metric.ValueType.INT)
+ .setDescription("Number of subflows").setDirection(Metric.DIRECTION_WORST).setQualitative(false)
+ .setDomain(org.sonar.api.measures.CoreMetrics.DOMAIN_SIZE).create();
+
+ public static final Metric TRANSFORMATIONS = new Metric.Builder("mule_dw", "DW Transformations",
+ Metric.ValueType.INT).setDescription("Number of subflows").setDirection(Metric.DIRECTION_WORST)
+ .setQualitative(false).setDomain(org.sonar.api.measures.CoreMetrics.DOMAIN_SIZE).create();
+
+ public static final Metric CONFIGURATION_FILES_COMP_RATING = new Metric.Builder(
+ "mule_configuration_complexity_rating", "Configuration File Complexity", Metric.ValueType.RATING)
+ .setDescription("Rating based on complexity of configuration file")
+ .setDirection(Metric.DIRECTION_BETTER).setQualitative(true).setDomain(CoreMetrics.DOMAIN_COMPLEXITY)
+ .create();
+
+ public static final Metric CONFIGURATION_FILES = new Metric.Builder("mule_configuration_files",
+ "Configuration Files", Metric.ValueType.INT).setDescription("Number of configuration files")
+ .setDirection(Metric.DIRECTION_WORST).setQualitative(false)
+ .setDomain(org.sonar.api.measures.CoreMetrics.DOMAIN_SIZE).create();
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public List getMetrics() {
+ return Arrays.asList(FLOWS, SUBFLOWS, CONFIGURATION_FILES_COMP_RATING, TRANSFORMATIONS, CONFIGURATION_FILES);
+ }
+
+}
diff --git a/src/main/java/com/mulesoft/services/tools/sonarqube/profile/MuleQualityProfile.java b/src/main/java/com/mulesoft/services/tools/sonarqube/profile/MuleQualityProfile.java
new file mode 100644
index 0000000..7a3c30c
--- /dev/null
+++ b/src/main/java/com/mulesoft/services/tools/sonarqube/profile/MuleQualityProfile.java
@@ -0,0 +1,69 @@
+package com.mulesoft.services.tools.sonarqube.profile;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.xml.bind.JAXBException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition;
+
+import com.mulesoft.services.tools.sonarqube.language.MuleLanguage;
+import com.mulesoft.services.tools.sonarqube.rule.MuleRulesDefinition;
+import com.mulesoft.services.tools.validation.consumer.RuleFactory;
+import com.mulesoft.services.tools.validation.rules.Ruleset;
+import com.mulesoft.services.tools.validation.rules.Rulestore;
+
+/**
+ * Mule Quality Profile
+ *
+ * @author franco.perez
+ *
+ */
+public class MuleQualityProfile implements BuiltInQualityProfilesDefinition {
+
+ Logger logger = LoggerFactory.getLogger(getClass());
+
+ @Override
+ public void define(Context context) {
+ if (logger.isDebugEnabled())
+ logger.debug("Creating MuleSoft Profiles");
+
+ // Mule3
+ NewBuiltInQualityProfile profile3 = context.createBuiltInQualityProfile("MuleSoft Rules for Mule 3.x",
+ MuleLanguage.LANGUAGE_KEY);
+ // profile3.setDefault(true);
+ activeRule(profile3, "mule3-repository", "file:extensions/plugins/rules-3.xml");
+ profile3.done();
+
+ // Mule4
+ NewBuiltInQualityProfile profile4 = context.createBuiltInQualityProfile("MuleSoft Rules for Mule 4.x",
+ MuleLanguage.LANGUAGE_KEY);
+ profile4.setDefault(true);
+ activeRule(profile4, "mule4-repository", "file:extensions/plugins/rules-4.xml");
+ profile4.done();
+ }
+
+ private void activeRule(NewBuiltInQualityProfile profile, String repositoryKey, String ruleFilename) {
+ try {
+ Rulestore rules = RuleFactory.loadRulesFromXml(ruleFilename);
+ List rulesetList = rules.getRuleset();
+
+ for (Iterator iterator = rulesetList.iterator(); iterator.hasNext();) {
+ Ruleset ruleset = iterator.next();
+ List ruleList = ruleset.getRule();
+ for (Iterator ruleIterator = ruleList
+ .iterator(); ruleIterator.hasNext();) {
+ com.mulesoft.services.tools.validation.rules.Rule rule = ruleIterator.next();
+ profile.activateRule(repositoryKey, MuleRulesDefinition.getRuleKey(ruleset, rule));
+ }
+ }
+ } catch (JAXBException | IOException e) {
+ logger.error(e.getMessage(), e);
+ throw new RuntimeException(e.getMessage(), e);
+ }
+ }
+
+}
diff --git a/src/main/java/com/mulesoft/services/tools/sonarqube/properties/MuleProperties.java b/src/main/java/com/mulesoft/services/tools/sonarqube/properties/MuleProperties.java
new file mode 100644
index 0000000..c3fdb75
--- /dev/null
+++ b/src/main/java/com/mulesoft/services/tools/sonarqube/properties/MuleProperties.java
@@ -0,0 +1,49 @@
+package com.mulesoft.services.tools.sonarqube.properties;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.mulesoft.services.tools.sonarqube.language.MuleLanguage;
+
+public class MuleProperties {
+
+ private static final String MULE_PROP = "mule.properties";
+ private static final String MULE4_PROP = "mule4.properties";
+ private static final String MULE3_PROP = "mule3.properties";
+
+ static private Logger logger = LoggerFactory.getLogger(MuleProperties.class);
+
+ static private Map props = new HashMap();
+
+ private static Properties loadProp(String language, String propName) {
+ Properties properties = new Properties();
+ try (InputStream input = MuleProperties.class.getClassLoader().getResourceAsStream(propName)) {
+ properties.load(input);
+ } catch (IOException e) {
+ logger.error(e.getMessage(), e);
+ }
+ props.put(language, properties);
+ return properties;
+ }
+
+ public static Properties getProperties(String language) {
+ if (props.containsKey(language)) {
+ return props.get(language);
+ } else {
+ switch (language) {
+ case MuleLanguage.LANGUAGE_MULE4_KEY:
+ return loadProp(language, MULE4_PROP);
+ case MuleLanguage.LANGUAGE_MULE3_KEY:
+ return loadProp(language, MULE3_PROP);
+ default:
+ return loadProp(language, MULE_PROP);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/mulesoft/services/tools/sonarqube/rule/MuleRulesDefinition.java b/src/main/java/com/mulesoft/services/tools/sonarqube/rule/MuleRulesDefinition.java
new file mode 100644
index 0000000..fbc44cf
--- /dev/null
+++ b/src/main/java/com/mulesoft/services/tools/sonarqube/rule/MuleRulesDefinition.java
@@ -0,0 +1,174 @@
+package com.mulesoft.services.tools.sonarqube.rule;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Properties;
+
+import javax.xml.bind.JAXBException;
+
+import org.sonar.api.rule.RuleStatus;
+import org.sonar.api.rule.Severity;
+import org.sonar.api.rules.RuleType;
+import org.sonar.api.server.rule.RuleParamType;
+import org.sonar.api.server.rule.RulesDefinition;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+import com.mulesoft.services.tools.sonarqube.language.MuleLanguage;
+import com.mulesoft.services.tools.sonarqube.properties.MuleProperties;
+import com.mulesoft.services.tools.validation.Constants;
+import com.mulesoft.services.tools.validation.Constants.Severities;
+import com.mulesoft.services.tools.validation.Constants.Types;
+import com.mulesoft.services.tools.validation.consumer.RuleFactory;
+import com.mulesoft.services.tools.validation.rules.Ruleset;
+import com.mulesoft.services.tools.validation.rules.Rulestore;
+
+public class MuleRulesDefinition implements RulesDefinition {
+
+ private final Logger logger = Loggers.get(MuleRulesDefinition.class);
+
+ public static String MULE3_REPOSITORY_KEY = "mule3-repository";
+ public static String MULE4_REPOSITORY_KEY = "mule4-repository";
+ public static final String RULE_TEMPLATE_KEY = "mule-rule-template";
+
+ public interface PARAMS {
+ String CATEGORY = "category";
+ String SCOPE = "scope";
+ String XPATH = "xpath-expression";
+ }
+
+ @Override
+ public void define(Context context) {
+ if (logger.isDebugEnabled())
+ logger.debug("Defining MuleSoft Rule - Based on Mule-Validation-XPath-Core rules");
+
+ logger.info("Working Directory = {}", System.getProperty("user.dir"));
+
+ createRepository(context, MULE3_REPOSITORY_KEY, MuleLanguage.LANGUAGE_KEY, "Mule3 Analyzer",
+ "file:extensions/plugins/rules-3.xml");
+ createRepository(context, MULE4_REPOSITORY_KEY, MuleLanguage.LANGUAGE_KEY, "Mule4 Analyzer",
+ "file:extensions/plugins/rules-4.xml");
+
+ }
+
+ private void createRepository(Context context, String repositoryKey, String language, String repositoryName,
+ String ruleFilename) {
+ NewRepository repository = context.createRepository(repositoryKey, language).setName(repositoryName);
+ try {
+ Rulestore rulestore = RuleFactory.loadRulesFromXml(ruleFilename);
+ List rulesetList = rulestore.getRuleset();
+
+ for (Iterator iterator = rulesetList.iterator(); iterator.hasNext();) {
+ Ruleset ruleset = iterator.next();
+ logger.debug("Rule Category : " + ruleset.getCategory());
+ List ruleList = ruleset.getRule();
+ for (Iterator ruleIterator = ruleList
+ .iterator(); ruleIterator.hasNext();) {
+ com.mulesoft.services.tools.validation.rules.Rule rule = ruleIterator.next();
+ logger.debug("Rule Id : " + rule.getId());
+ addRule(repository, ruleset, rule, language);
+ }
+
+ }
+
+ } catch (JAXBException | IOException e) {
+ logger.error(e.getMessage(), e);
+ throw new RuntimeException(e.getMessage(), e);
+ }
+
+ addRuleTemplate(repository, language);
+ repository.rule(RULE_TEMPLATE_KEY).setTemplate(true);
+ // don't forget to call done() to finalize the definition
+ repository.done();
+
+ }
+
+ private void addRuleTemplate(NewRepository repository, String language) {
+
+ Properties prop = (repository.key().equals(MULE3_REPOSITORY_KEY)
+ ? MuleProperties.getProperties(MuleLanguage.LANGUAGE_MULE3_KEY)
+ : MuleProperties.getProperties(MuleLanguage.LANGUAGE_MULE4_KEY));
+ NewRule x1Rule = repository.createRule(RULE_TEMPLATE_KEY).setName(prop.getProperty("rule.template.name"))
+ .setHtmlDescription(prop.getProperty("rule.template.description"));
+
+ x1Rule.addTags(language);
+ x1Rule.createParam(PARAMS.CATEGORY).setDescription(prop.getProperty("rule.template.parameter.category"))
+ .setType(RuleParamType.STRING);
+ x1Rule.createParam(PARAMS.XPATH).setDescription(prop.getProperty("rule.template.parameter.xpath"))
+ .setType(RuleParamType.STRING);
+ x1Rule.createParam(PARAMS.SCOPE).setDescription(prop.getProperty("rule.template.parameter.scope"))
+ .setType(RuleParamType.STRING);
+ }
+
+ private void addRule(NewRepository repository, Ruleset ruleset,
+ com.mulesoft.services.tools.validation.rules.Rule rule, String language) {
+
+ NewRule x1Rule = repository.createRule(MuleRulesDefinition.getRuleKey(ruleset, rule)).setName(rule.getName())
+ .setHtmlDescription(rule.getDescription()).setActivatedByDefault(true)
+ .setHtmlDescription(rule.getDescription()).setStatus(RuleStatus.READY);
+
+ x1Rule.setSeverity(getSeverity(rule));
+ x1Rule.setType(getType(rule));
+ x1Rule.addTags(language);
+ x1Rule.createParam(PARAMS.CATEGORY).setDefaultValue(ruleset.getCategory()).setType(RuleParamType.STRING);
+ x1Rule.createParam(PARAMS.XPATH).setDefaultValue(rule.getValue()).setType(RuleParamType.STRING);
+ if (rule.getApplies() != null) {
+ x1Rule.createParam(PARAMS.SCOPE).setDefaultValue(rule.getApplies()).setType(RuleParamType.STRING);
+ }
+
+ }
+
+ private String getSeverity(com.mulesoft.services.tools.validation.rules.Rule rule) {
+ Severities sev = Severities.valueOf(rule.getSeverity());
+ String severity;
+ switch (sev) {
+ case BLOCKER:
+ severity = Severity.BLOCKER;
+ break;
+ case CRITICAL:
+ severity = Severity.CRITICAL;
+ break;
+ case MAJOR:
+ severity = Severity.MAJOR;
+ break;
+ case MINOR:
+ severity = Severity.MINOR;
+ break;
+ case INFO:
+ severity = Severity.INFO;
+ break;
+ default:
+ severity = Severity.INFO;
+ break;
+ }
+ return severity;
+ }
+
+ private RuleType getType(com.mulesoft.services.tools.validation.rules.Rule rule) {
+ Types types = Types
+ .valueOf((rule.getType() != null ? rule.getType().toUpperCase() : Constants.Type.BUG.toUpperCase()));
+ RuleType type;
+ switch (types) {
+ case BUG:
+ type = RuleType.BUG;
+ break;
+ case CODE_SMELL:
+ type = RuleType.CODE_SMELL;
+ break;
+ case VULNERABILITY:
+ type = RuleType.VULNERABILITY;
+ break;
+ default:
+ type = RuleType.BUG;
+ break;
+ }
+ return type;
+ }
+
+ public static String getRuleKey(Ruleset ruleset, com.mulesoft.services.tools.validation.rules.Rule rule) {
+ return ruleset.getCategory() + "." + rule.getId();
+
+ }
+
+}
diff --git a/src/main/java/com/mulesoft/services/tools/sonarqube/rule/scope/ApplicationStrategyScope.java b/src/main/java/com/mulesoft/services/tools/sonarqube/rule/scope/ApplicationStrategyScope.java
new file mode 100644
index 0000000..86f4645
--- /dev/null
+++ b/src/main/java/com/mulesoft/services/tools/sonarqube/rule/scope/ApplicationStrategyScope.java
@@ -0,0 +1,70 @@
+package com.mulesoft.services.tools.sonarqube.rule.scope;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.jdom2.Document;
+import org.jdom2.Element;
+import org.jdom2.JDOMException;
+import org.jdom2.input.SAXBuilder;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.rule.ActiveRule;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.issue.NewIssue;
+import org.sonar.api.batch.sensor.issue.NewIssueLocation;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+import com.mulesoft.services.tools.sonarqube.rule.MuleRulesDefinition;
+import com.mulesoft.services.xpath.XPathProcessor;
+
+public class ApplicationStrategyScope implements ScopeStrategy {
+ private final Logger logger = Loggers.get(ApplicationStrategyScope.class);
+ SAXBuilder saxBuilder = new SAXBuilder();
+ Set valids = new HashSet();
+
+ @Override
+ public void validate(XPathProcessor xpathValidator, Map> issues, SensorContext context,
+ InputFile t, ActiveRule rule) {
+ try {
+ Document document = saxBuilder.build(t.inputStream());
+ Element rootElement = document.getRootElement();
+ String ruleId = rule.ruleKey().toString();
+ boolean valid = xpathValidator.processXPath(rule.param(MuleRulesDefinition.PARAMS.XPATH).trim(),
+ rootElement, Boolean.class).booleanValue();
+ logger.info("Validation Result: " + valid + " : File: " + t.filename() + " :Rule:" + rule.ruleKey());
+ if (!valid && !valids.contains(ruleId) && !issues.containsKey(rule.ruleKey())) {
+ NewIssue newIssue = context.newIssue().forRule(rule.ruleKey());
+ NewIssueLocation primaryLocation = newIssue.newLocation().on(t);
+ newIssue.at(primaryLocation);
+ addIssue(issues, rule, newIssue);
+ } else {
+ if (valid && !valids.contains(ruleId)) {
+ valids.add(ruleId);
+ issues.remove(rule.ruleKey());
+ }
+ }
+
+ } catch (JDOMException | IOException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+ private void addIssue(Map> issues, ActiveRule rule, NewIssue issue) {
+
+ if (issues.containsKey(rule.ruleKey())) {
+ issues.get(rule.ruleKey()).add(issue);
+ } else {
+ List issuesList = new ArrayList();
+ issuesList.add(issue);
+ issues.put(rule.ruleKey(), issuesList);
+ }
+
+ }
+
+}
diff --git a/src/main/java/com/mulesoft/services/tools/sonarqube/rule/scope/FileStrategyScope.java b/src/main/java/com/mulesoft/services/tools/sonarqube/rule/scope/FileStrategyScope.java
new file mode 100644
index 0000000..89428ac
--- /dev/null
+++ b/src/main/java/com/mulesoft/services/tools/sonarqube/rule/scope/FileStrategyScope.java
@@ -0,0 +1,60 @@
+package com.mulesoft.services.tools.sonarqube.rule.scope;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.jdom2.Document;
+import org.jdom2.Element;
+import org.jdom2.JDOMException;
+import org.jdom2.input.SAXBuilder;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.rule.ActiveRule;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.issue.NewIssue;
+import org.sonar.api.batch.sensor.issue.NewIssueLocation;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+import com.mulesoft.services.tools.sonarqube.rule.MuleRulesDefinition;
+import com.mulesoft.services.xpath.XPathProcessor;
+
+public class FileStrategyScope implements ScopeStrategy {
+ private final Logger logger = Loggers.get(FileStrategyScope.class);
+ SAXBuilder saxBuilder = new SAXBuilder();
+
+ @Override
+ public void validate(XPathProcessor xpathValidator, Map> issues, SensorContext context,
+ InputFile t, ActiveRule rule) {
+ try {
+ Document document = saxBuilder.build(t.inputStream());
+ Element rootElement = document.getRootElement();
+ boolean valid = xpathValidator.processXPath(rule.param(MuleRulesDefinition.PARAMS.XPATH).trim(),
+ rootElement, Boolean.class).booleanValue();
+ logger.info("Validation Result: " + valid + " : File: " + t.filename() + " :Rule:" + rule.ruleKey());
+ if (!valid) {
+ NewIssue newIssue = context.newIssue().forRule(rule.ruleKey());
+ NewIssueLocation primaryLocation = newIssue.newLocation().on(t);
+ newIssue.at(primaryLocation);
+ addIssue(issues, rule, newIssue);
+ }
+
+ } catch (JDOMException | IOException e) {
+ logger.error(e.getMessage(), e);
+ }
+ }
+
+ private void addIssue(Map> issues, ActiveRule rule, NewIssue issue) {
+
+ if (issues.containsKey(rule.ruleKey())) {
+ issues.get(rule.ruleKey()).add(issue);
+ } else {
+ List issuesList = new ArrayList();
+ issuesList.add(issue);
+ issues.put(rule.ruleKey(), issuesList);
+ }
+
+ }
+}
diff --git a/src/main/java/com/mulesoft/services/tools/sonarqube/rule/scope/ScopeFactory.java b/src/main/java/com/mulesoft/services/tools/sonarqube/rule/scope/ScopeFactory.java
new file mode 100644
index 0000000..aa07a82
--- /dev/null
+++ b/src/main/java/com/mulesoft/services/tools/sonarqube/rule/scope/ScopeFactory.java
@@ -0,0 +1,32 @@
+package com.mulesoft.services.tools.sonarqube.rule.scope;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.mulesoft.services.tools.validation.Constants;
+
+public class ScopeFactory {
+
+ private static ScopeFactory instance = null;
+
+ private Map strategies = new HashMap();
+
+ public static ScopeFactory getInstance() {
+ if (instance == null) {
+ instance = new ScopeFactory();
+ }
+ return instance;
+ }
+
+ public ScopeFactory() {
+ strategies.put(ScopeStrategy.FILE, new FileStrategyScope());
+ strategies.put(ScopeStrategy.APPLICATION, new ApplicationStrategyScope());
+ }
+
+ public ScopeStrategy getStrategy(String scope) {
+ if (Constants.Applicability.APPLICATION.equals(scope)) {
+ return strategies.get(ScopeStrategy.APPLICATION);
+ }
+ return strategies.get(ScopeStrategy.FILE);
+ }
+}
diff --git a/src/main/java/com/mulesoft/services/tools/sonarqube/rule/scope/ScopeStrategy.java b/src/main/java/com/mulesoft/services/tools/sonarqube/rule/scope/ScopeStrategy.java
new file mode 100644
index 0000000..b20ef72
--- /dev/null
+++ b/src/main/java/com/mulesoft/services/tools/sonarqube/rule/scope/ScopeStrategy.java
@@ -0,0 +1,20 @@
+package com.mulesoft.services.tools.sonarqube.rule.scope;
+
+import java.util.List;
+import java.util.Map;
+
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.rule.ActiveRule;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.issue.NewIssue;
+import org.sonar.api.rule.RuleKey;
+
+import com.mulesoft.services.xpath.XPathProcessor;
+
+public interface ScopeStrategy {
+
+ String FILE = "file";
+ String APPLICATION = "application";
+
+ public void validate(XPathProcessor xpathValidator, Map> issues, SensorContext context, InputFile t, ActiveRule rule);
+}
diff --git a/src/main/java/com/mulesoft/services/tools/sonarqube/sensor/MuleSensor.java b/src/main/java/com/mulesoft/services/tools/sonarqube/sensor/MuleSensor.java
new file mode 100644
index 0000000..b8de4d6
--- /dev/null
+++ b/src/main/java/com/mulesoft/services/tools/sonarqube/sensor/MuleSensor.java
@@ -0,0 +1,67 @@
+package com.mulesoft.services.tools.sonarqube.sensor;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.jdom2.input.SAXBuilder;
+import org.sonar.api.batch.fs.FileSystem;
+import org.sonar.api.batch.sensor.Sensor;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.SensorDescriptor;
+import org.sonar.api.batch.sensor.issue.NewIssue;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+import com.mulesoft.services.tools.sonarqube.filter.MuleFilePredicate;
+import com.mulesoft.services.tools.sonarqube.language.MuleLanguage;
+import com.mulesoft.services.tools.sonarqube.rule.MuleRulesDefinition;
+
+/**
+ * Mule Sensor Iterates over all mule files and applies the corresponding rules
+ *
+ * @author franco.perez
+ *
+ */
+public class MuleSensor implements Sensor {
+
+ private final Logger logger = Loggers.get(MuleSensor.class);
+
+ SAXBuilder saxBuilder = new SAXBuilder();
+
+ @Override
+ public void describe(SensorDescriptor descriptor) {
+ descriptor.onlyOnLanguage(MuleLanguage.LANGUAGE_KEY);
+ descriptor.createIssuesForRuleRepositories(MuleRulesDefinition.MULE3_REPOSITORY_KEY,
+ MuleRulesDefinition.MULE4_REPOSITORY_KEY);
+ }
+
+ @Override
+ public void execute(SensorContext context) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Executing Mule Sensor");
+ }
+
+ FileSystem fs = context.fileSystem();
+
+ Map> issues = new HashMap>();
+ fs.inputFiles(new MuleFilePredicate()).forEach(new SonarRuleConsumer(getLanguage(context), context, issues));
+
+ // Iterate and save all the issues
+ for (Iterator> newIssueListIterator = issues.values().iterator(); newIssueListIterator
+ .hasNext();) {
+ List newIssueList = newIssueListIterator.next();
+ for (Iterator newIssueIterator = newIssueList.iterator(); newIssueIterator.hasNext();) {
+ NewIssue newIssue = newIssueIterator.next();
+ newIssue.save();
+ }
+ }
+ }
+
+ public static String getLanguage(SensorContext context) {
+ boolean mule4 = context.activeRules().findByRepository(MuleRulesDefinition.MULE3_REPOSITORY_KEY).isEmpty();
+ return mule4 ? MuleLanguage.LANGUAGE_MULE4_KEY : MuleLanguage.LANGUAGE_MULE3_KEY;
+ }
+}
diff --git a/src/main/java/com/mulesoft/services/tools/sonarqube/sensor/SonarRuleConsumer.java b/src/main/java/com/mulesoft/services/tools/sonarqube/sensor/SonarRuleConsumer.java
new file mode 100644
index 0000000..ae69a1f
--- /dev/null
+++ b/src/main/java/com/mulesoft/services/tools/sonarqube/sensor/SonarRuleConsumer.java
@@ -0,0 +1,98 @@
+package com.mulesoft.services.tools.sonarqube.sensor;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.rule.ActiveRule;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.issue.NewIssue;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+import com.mulesoft.services.tools.sonarqube.language.MuleLanguage;
+import com.mulesoft.services.tools.sonarqube.properties.MuleProperties;
+import com.mulesoft.services.tools.sonarqube.rule.MuleRulesDefinition;
+import com.mulesoft.services.tools.sonarqube.rule.scope.ScopeFactory;
+import com.mulesoft.services.xpath.XPathProcessor;
+
+/**
+ * Consumer to validate an InputFile against all the rules
+ *
+ * @author franco.parma
+ *
+ */
+public class SonarRuleConsumer implements Consumer {
+
+ private final Logger logger = Loggers.get(SonarRuleConsumer.class);
+ SensorContext context;
+ Map> issues;
+ String language;
+ XPathProcessor xpathProcessor;
+ List filteredCategories = new ArrayList();
+
+ private static final String FILTER_RULESET_PROPERTY = "sonar.property.ruleset.categories";
+
+ public SonarRuleConsumer(String language, SensorContext context, Map> issues) {
+ this.language = language;
+ this.context = context;
+ this.issues = issues;
+ xpathProcessor = new XPathProcessor().loadNamespaces(
+ language.equals(MuleLanguage.LANGUAGE_MULE4_KEY) ? "namespace-4.properties" : "namespace-3.properties");
+
+ String categories = (String) MuleProperties.getProperties(MuleLanguage.LANGUAGE_KEY)
+ .get(FILTER_RULESET_PROPERTY);
+ Optional categoriesVariable = this.context.config().get(categories);
+ if (categoriesVariable.isPresent()) {
+ filteredCategories = Arrays.asList(categoriesVariable.get().split(","));
+ }
+ }
+
+ @Override
+ public void accept(InputFile t) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Validating file:" + t.filename());
+ }
+
+ Collection activeRules = this.context.activeRules().findByLanguage(MuleLanguage.LANGUAGE_KEY);
+
+ Iterator iterator = rulesIterator(activeRules);
+
+ while (iterator.hasNext()) {
+ ActiveRule rule = iterator.next();
+ if (logger.isDebugEnabled()) {
+ logger.debug("Validating rule:" + rule.internalKey());
+ }
+ String appliesTo = rule.param(MuleRulesDefinition.PARAMS.SCOPE);
+
+ ScopeFactory.getInstance().getStrategy(appliesTo).validate(xpathProcessor, issues, context, t, rule);
+ }
+ }
+
+ private Iterator rulesIterator(Collection activeRules) {
+ Iterator iterator;
+ if (!filteredCategories.isEmpty()) {
+ iterator = activeRules.stream().filter(new CategoryPredicate()).iterator();
+ } else {
+ iterator = activeRules.iterator();
+ }
+ return iterator;
+ }
+
+ class CategoryPredicate implements Predicate {
+ @Override
+ public boolean test(ActiveRule t) {
+ String ruleCategory = t.param(MuleRulesDefinition.PARAMS.CATEGORY);
+ return filteredCategories.contains(ruleCategory);
+ }
+ }
+
+}
diff --git a/src/main/resources/com/mulesoft/services/tools/sonarqube/profile/profile-mule.xml b/src/main/resources/com/mulesoft/services/tools/sonarqube/profile/profile-mule.xml
new file mode 100755
index 0000000..d93bec3
--- /dev/null
+++ b/src/main/resources/com/mulesoft/services/tools/sonarqube/profile/profile-mule.xml
@@ -0,0 +1,4 @@
+
+ MuleSoft Profile
+ mule
+
\ No newline at end of file
diff --git a/src/main/resources/mule.properties b/src/main/resources/mule.properties
new file mode 100644
index 0000000..d37e7f3
--- /dev/null
+++ b/src/main/resources/mule.properties
@@ -0,0 +1,3 @@
+## CONFIGURATION
+sonar.property.language=sonar.mule.language
+sonar.property.ruleset.categories=sonar.mule.ruleset.categories
diff --git a/src/main/resources/mule3.properties b/src/main/resources/mule3.properties
new file mode 100644
index 0000000..5dab45f
--- /dev/null
+++ b/src/main/resources/mule3.properties
@@ -0,0 +1,28 @@
+## PLUGIN
+mule.munit.properties.name=name
+mule.munit.properties.flows=flows
+mule.munit.properties.files=files
+mule.munit.properties.coverage=coverage
+mule.munit.properties.processorCount=messageProcessorCount
+mule.munit.properties.coveredProcessorCount=coveredMessageProcessorCount
+mule.munit.properties.lines=lines
+mule.munit.properties.lineNumber=lineNumber
+mule.munit.properties.covered=covered
+
+## METRICS
+
+## CONFIGURATION
+sonar.property.ruleset.categories=sonar.mule.ruleset.categories
+
+## Labels
+rule.template.name=Track issues on Mule 3
+rule.template.description=Rule Template for mule 3
+rule.template.parameter.scope=The scope of the rules. It should either file or application
+rule.template.parameter.xpath=XPath expression to be evaluated
+rule.template.parameter.category=Category of the rule
+
+
+mule.metric.dw.payload=count(//dw:transform-message/dw:set-payload)
+mule.metric.dw.variable=count(//dw:transform-message/dw:set-variable)
+mule.metric.flow=count(//mule:mule/mule:flow)
+mule.metric.subflow=count(//mule:mule/mule:sub-flow)
\ No newline at end of file
diff --git a/src/main/resources/mule4.properties b/src/main/resources/mule4.properties
new file mode 100644
index 0000000..3b76f62
--- /dev/null
+++ b/src/main/resources/mule4.properties
@@ -0,0 +1,28 @@
+## PLUGIN
+mule.munit.properties.name=name
+mule.munit.properties.flows=flows
+mule.munit.properties.files=files
+mule.munit.properties.coverage=coverage
+mule.munit.properties.processorCount=messageProcessorCount
+mule.munit.properties.coveredProcessorCount=coveredProcessorCount
+mule.munit.properties.lines=lines
+mule.munit.properties.lineNumber=lineNumber
+mule.munit.properties.covered=covered
+
+## METRICS
+
+## CONFIGURATION
+sonar.property.ruleset.categories=sonar.mule.ruleset.categories
+
+## Labels
+rule.template.name=Track issues on Mule 4
+rule.template.description=Rule Template for mule 4
+rule.template.parameter.scope=The scope of the rules. It should either file or application
+rule.template.parameter.xpath=XPath expression to be evaluated
+rule.template.parameter.category=Category of the rule
+
+
+mule.metric.dw.payload=count(//ee:transform/ee:message/ee:set-payload)
+mule.metric.dw.variable=count(//ee:transform/ee:variables/ee:set-variable)
+mule.metric.flow=count(//mule:mule/mule:flow)
+mule.metric.subflow=count(//mule:mule/mule:sub-flow)
diff --git a/src/test/java/com/mulesoft/services/tools/sonarqube/AppTest.java b/src/test/java/com/mulesoft/services/tools/sonarqube/AppTest.java
new file mode 100644
index 0000000..25e96a6
--- /dev/null
+++ b/src/test/java/com/mulesoft/services/tools/sonarqube/AppTest.java
@@ -0,0 +1,38 @@
+package com.mulesoft.services.tools.sonarqube;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Unit test for simple App.
+ */
+public class AppTest
+ extends TestCase
+{
+ /**
+ * Create the test case
+ *
+ * @param testName name of the test case
+ */
+ public AppTest( String testName )
+ {
+ super( testName );
+ }
+
+ /**
+ * @return the suite of tests being tested
+ */
+ public static Test suite()
+ {
+ return new TestSuite( AppTest.class );
+ }
+
+ /**
+ * Rigourous Test :-)
+ */
+ public void testApp()
+ {
+ assertTrue( true );
+ }
+}