diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml
index 81afd7b7a..ac302b605 100644
--- a/.github/workflows/checks.yml
+++ b/.github/workflows/checks.yml
@@ -22,6 +22,14 @@ jobs:
with:
distribution: 'temurin'
java-version: '17'
+ cache: maven
+ server-id: pixee
+ server-username: ${{ vars.PIXEE_ARTIFACTORY_USERNAME }}
+ server-password: ${{ secrets.PIXEE_ARTIFACTORY_PASSWORD }}
+ - name: Set up Maven
+ uses: stCarolas/setup-maven@v4.4
+ with:
+ maven-version: 3.8.2
- uses: gradle/gradle-build-action@v2
with:
arguments: check --stacktrace
diff --git a/gradle/build-plugins/build.gradle.kts b/gradle/build-plugins/build.gradle.kts
index d9fd4b11c..c78f390fb 100644
--- a/gradle/build-plugins/build.gradle.kts
+++ b/gradle/build-plugins/build.gradle.kts
@@ -24,4 +24,5 @@ dependencies {
implementation(buildlibs.spotless)
implementation(buildlibs.nebula.publish.plugin)
implementation(buildlibs.nebula.contacts.plugin)
+ implementation(buildlibs.kotlin)
}
diff --git a/gradle/build-plugins/src/main/kotlin/io.codemodder.kotlin.gradle.kts b/gradle/build-plugins/src/main/kotlin/io.codemodder.kotlin.gradle.kts
new file mode 100644
index 000000000..f05d74fee
--- /dev/null
+++ b/gradle/build-plugins/src/main/kotlin/io.codemodder.kotlin.gradle.kts
@@ -0,0 +1,20 @@
+plugins {
+ id("io.codemodder.spotless")
+ id("org.jetbrains.kotlin.jvm")
+}
+
+java {
+ toolchain {
+ languageVersion.set(JavaLanguageVersion.of(17))
+ }
+}
+
+spotless {
+ java {
+ googleJavaFormat()
+ }
+}
+
+tasks.withType(Test::class) {
+ useJUnitPlatform()
+}
diff --git a/gradle/buildlibs.versions.toml b/gradle/buildlibs.versions.toml
index f9385bc61..d73324bf5 100644
--- a/gradle/buildlibs.versions.toml
+++ b/gradle/buildlibs.versions.toml
@@ -9,6 +9,7 @@ nebula-contacts-plugin = { module = "com.netflix.nebula.contacts:com.netflix.neb
nebula-mavenscm-plugin = { module = "com.netflix.nebula.maven-scm:com.netflix.nebula.maven-scm.gradle.plugin", version = "20.3.0"}
nebula-publish-plugin = { module = "com.netflix.nebula.maven-publish:com.netflix.nebula.maven-publish.gradle.plugin", version = "20.3.0"}
spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" }
+kotlin = { module = "org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin", version = "1.8.20"}
[plugins]
fileversioning = { id = "de.epitschke.gradle-file-versioning", version.ref = "fileversioning" }
diff --git a/pom-operator/.github/badges/branches.svg b/pom-operator/.github/badges/branches.svg
new file mode 100644
index 000000000..1d4e5797d
--- /dev/null
+++ b/pom-operator/.github/badges/branches.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/pom-operator/.github/badges/jacoco.svg b/pom-operator/.github/badges/jacoco.svg
new file mode 100644
index 000000000..d17718247
--- /dev/null
+++ b/pom-operator/.github/badges/jacoco.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/pom-operator/.github/pull_request_template.md b/pom-operator/.github/pull_request_template.md
new file mode 100644
index 000000000..647353c41
--- /dev/null
+++ b/pom-operator/.github/pull_request_template.md
@@ -0,0 +1,15 @@
+# Description
+
+# Checklist:
+
+- [ ] I did my own review
+- [ ] Commented Code - particularly in hard sections
+- [ ] Docs (Javadocs)
+- [ ] No TODOs / Issues on Code
+- [ ] Tests: Unit Testing
+- [ ] Tests: Assertions Look Good and Sane
+- [ ] Tests: Coverage
+- [ ] Integration Testing
+- [ ] Java Test Fixtures
+- [ ] Security Risks Considered and Addressed / Mitigated
+
\ No newline at end of file
diff --git a/pom-operator/.github/workflows/push-test.yml b/pom-operator/.github/workflows/push-test.yml
new file mode 100644
index 000000000..2504f91ef
--- /dev/null
+++ b/pom-operator/.github/workflows/push-test.yml
@@ -0,0 +1,81 @@
+name: Java CI
+
+on:
+ push:
+ branches-ignore:
+ - 'master'
+
+env:
+ MAVEN_OPTS: "-Dhttps.protocols=TLSv1.2"
+
+jobs:
+ build:
+ strategy:
+ matrix:
+ platform: [ubuntu-latest, macos-latest]
+ runs-on: ${{ matrix.platform }}
+ environment:
+ name: default
+ env:
+ ARTIFACTORY_USERNAME_REF: "${{ secrets.ARTIFACTORY_USERNAME_REF }}"
+ ARTIFACTORY_TOKEN_REF: "${{ secrets.ARTIFACTORY_TOKEN_REF }}"
+ steps:
+ - uses: actions/checkout@v3
+ - name: Set up JDK 11
+ uses: actions/setup-java@v3
+ with:
+ java-version: '8'
+ distribution: 'temurin'
+ cache: maven
+ server-id: pixee
+ server-username: ARTIFACTORY_USERNAME_REF
+ server-password: ARTIFACTORY_TOKEN_REF
+ - name: Set up Maven
+ uses: stCarolas/setup-maven@v4.4
+ with:
+ maven-version: 3.8.2
+ - name: Resolve all Dependencies
+ run: |
+ mvn -N -B dependency:go-offline
+ - name: Build with Maven
+ run: |
+ mvn -N -B install
+ - name: Integration Testing (Linux Only)
+ if: matrix.platform == 'ubuntu-latest'
+ run: |
+ mvn -N -B -Dmaven.test.skip install
+ mvn -N -B -P integration-testing verify
+ - name: Generate Coverage Badge
+ id: jacoco
+ uses: cicirello/jacoco-badge-generator@v2
+ if: ${{ matrix.platform == 'ubuntu-latest' }}
+ with:
+ generate-branches-badge: true
+ - name: Log coverage percentage
+ if: ${{ matrix.platform == 'ubuntu-latest' }}
+ run: |
+ echo "coverage = ${{ steps.jacoco.outputs.coverage }}"
+ echo "branch coverage = ${{ steps.jacoco.outputs.branches }}"
+ - # When using "Act", disable certain actions, starting with this one
+ # See https://github.com/nektos/act#skipping-steps for more details
+ name: Commit the JaCoCo badge (if it changed)
+ if: ${{ matrix.platform == 'ubuntu-latest' && !env.ACT }}
+ run: |
+ if [[ `git status --porcelain` ]]; then
+ git config --global user.name 'Jacoco Coverage Update Action'
+ git config --global user.email 'pixee@users.noreply.github.com'
+ git add -A
+ git commit -m "[no ci] Autogenerated JaCoCo coverage badge"
+ git push
+ fi
+ - name: Upload JaCoCo coverage report
+ if: ${{ matrix.platform == 'ubuntu-latest' && !env.ACT }}
+ uses: actions/upload-artifact@v2
+ with:
+ name: jacoco-report
+ path: target/site/jacoco/
+
+ - name: Deploy (only if on master)
+ if: ${{ github.ref_name == 'master' && matrix.platform == 'ubuntu-latest' && !env.ACT }}
+ run: |
+ mvn -N -B deploy -DaltDeploymentRepository=pixee::default::https://pixee.jfrog.io/artifactory/mailman-libs-snapshot
diff --git a/pom-operator/.github/workflows/release.yml b/pom-operator/.github/workflows/release.yml
new file mode 100644
index 000000000..ff8116272
--- /dev/null
+++ b/pom-operator/.github/workflows/release.yml
@@ -0,0 +1,48 @@
+name: Create Release on Semantic Version Tag
+
+on:
+ push:
+ tags:
+ - "v*"
+
+env:
+ M2_HOME: /opt/hostedtoolcache/maven/3.8.2
+ JAVA_HOME: /opt/hostedtoolcache/Java_Adopt_jdk/11.0.17-8
+
+jobs:
+ release:
+ runs-on: "ubuntu-latest"
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - id: setup-java
+ name: "Build, test and deploy"
+ uses: actions/setup-java@v2
+ with:
+ java-version: '11'
+ distribution: 'adopt'
+ server-id: ossrh
+ server-username: MAVEN_USERNAME
+ server-password: MAVEN_PASSWORD
+ gpg-private-key: ${{ secrets.OSSRH_GPG_SECRET_KEY }}
+ gpg-passphrase: MAVEN_GPG_PASSPHRASE
+
+ - name: Set up Maven
+ uses: stCarolas/setup-maven@v4.4
+ with:
+ maven-version: 3.8.2
+
+ - name: "Publish to Maven Central"
+ run: |
+ mvn -N -B \
+ -Prelease \
+ -X \
+ --no-transfer-progress \
+ --batch-mode \
+ clean \
+ deploy
+ env:
+ MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }}
+ MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }}
+ MAVEN_GPG_PASSPHRASE: ${{ secrets.OSSRH_GPG_SECRET_KEY_PASSPHRASE }}
diff --git a/pom-operator/.gitignore b/pom-operator/.gitignore
new file mode 100644
index 000000000..b8115cd81
--- /dev/null
+++ b/pom-operator/.gitignore
@@ -0,0 +1,69 @@
+### Maven template
+.cache/
+
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+# https://github.com/takari/maven-wrapper#usage-without-binary-jar
+.mvn/wrapper/maven-wrapper.jar
+
+### JetBrains template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+.secrets
diff --git a/pom-operator/README.md b/pom-operator/README.md
new file mode 100644
index 000000000..ff4274fda
--- /dev/null
+++ b/pom-operator/README.md
@@ -0,0 +1,68 @@
+[![Actions Status](https://github.com/pixee/pom-operator/workflows/Java%20CI/badge.svg)](https://github.com/pixee/pom-operator/actions)
+![Coverage](.github/badges/jacoco.svg)
+
+# pom-operator
+
+POM Operator is a library responsible for injecting dependencies into POM files programatically.
+
+## Building
+
+Use [Maven](https://maven.apache.org):
+
+```
+$ git clone git@github.com:pixee/pom-operator.git && cd pom-operator
+$ mvn clean install
+```
+
+## Using
+
+There's a sample of usage from Java on the `java-sample` directory - of look under the `src/test` directory as well. TL;DR:
+
+```java
+import org.junit.Test;
+
+import io.github.pixee.maven.operator.ProjectModel;
+import io.github.pixee.maven.operator.Dependency;
+import io.github.pixee.maven.operator.POMOperator;
+import io.github.pixee.maven.operator.ProjectModelFactory;
+
+public class POMOperatorJavaTest {
+ @Test
+ public void testInterop() {
+ ProjectModel projectModel = ProjectModelFactory
+ .load(POMOperatorJavaTest.class.getResource("pom.xml"))
+ .withDependency(
+ new Dependency("org.dom4j", "dom4j", "0.0.0", null, "jar")
+ );
+
+ POMOperator.modify(projectModel);
+ }
+}
+
+```
+
+## How it works?
+
+It implements a Chain of Responsibility strategy - each `Command` class attempts a different way of fixing a POM, based around a Context (in this case, a `ProjectModel`)
+
+## Releasing
+
+e.g. to generate version `0.0.2`:
+
+```
+(mvn versions:set -DnewVersion=0.0.3 && git commit -am "Generating Tag" && git tag v0.0.3 && git push && git push --tags)
+(export V='0.0.11-SNAPSHOT' ; mvn versions:set -DnewVersion=${V} && (cd java-sample ; mvn versions:set -DnewVersion=${V} && git commit -am "Generating development version" && git push))
+```
+
+# TODO:
+
+Deploying:
+
+```
+mvn -N -B deploy -DaltDeploymentRepository=pixee-libs-release::default::https://pixee.jfrog.io/artifactory/default-maven-local
+```
+
+- ~~better readme~~
+- ~~be able to guess existing indenting for existing documents~~
+- ~~investigate leverage whats on [versions-maven-plugin](https://github.com/mojohaus/versions-maven-plugin)~~
+- consider fuzzying when testing
diff --git a/pom-operator/build.gradle.kts b/pom-operator/build.gradle.kts
new file mode 100644
index 000000000..f7f78420d
--- /dev/null
+++ b/pom-operator/build.gradle.kts
@@ -0,0 +1,43 @@
+plugins {
+ id("io.codemodder.kotlin")
+}
+
+dependencies {
+ api("com.offbytwo:docopt:0.6.0.20150202")
+ api("org.apache.commons:commons-lang3:3.12.0")
+ api("org.dom4j:dom4j:2.1.3")
+ api("jaxen:jaxen:1.2.0")
+ api("xerces:xercesImpl:2.12.2")
+ api("org.xmlunit:xmlunit-core:2.9.0")
+ api("org.xmlunit:xmlunit-assertj3:2.9.0")
+ api("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10")
+ api("com.github.zafarkhaja:java-semver:0.9.0")
+ api("commons-io:commons-io:2.11.0")
+ api("org.apache.maven.shared:maven-invoker:3.2.0")
+ api("com.google.inject:guice:5.1.0")
+ api("org.apache.maven:maven-embedder:3.8.6") {
+ exclude(group = "com.google.inject", module = "guice")
+ }
+ api("org.apache.maven:maven-compat:3.8.6") {
+ exclude(group = "com.google.inject", module = "guice")
+ }
+ api("org.apache.maven.resolver:maven-resolver-api:1.9.2")
+ api("org.apache.maven.resolver:maven-resolver-spi:1.9.2")
+ api("org.apache.maven.resolver:maven-resolver-util:1.9.2")
+ api("org.apache.maven.resolver:maven-resolver-impl:1.9.2")
+ api("org.apache.maven.resolver:maven-resolver-transport-file:1.9.2")
+ api("org.apache.maven.resolver:maven-resolver-transport-http:1.9.2")
+ api("org.apache.maven.resolver:maven-resolver-connector-basic:1.9.2")
+ api("org.apache.maven:maven-resolver-provider:3.8.6")
+ api("org.apache.maven:maven-model-builder:3.8.6")
+ api("com.github.albfernandez:juniversalchardet:2.4.0")
+ testImplementation("org.slf4j:slf4j-simple:2.0.0")
+ testImplementation("fun.mike:diff-match-patch:0.0.2")
+ testImplementation("io.github.java-diff-utils:java-diff-utils:4.12")
+ testImplementation("org.hamcrest:hamcrest-all:1.3")
+ testImplementation(testlibs.bundles.junit.jupiter)
+ compileOnly("org.slf4j:slf4j-api:2.0.0")
+ testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
+ testImplementation("org.junit.jupiter:junit-jupiter-engine:5.9.2")
+ testRuntimeOnly("org.junit.platform:junit-platform-launcher")
+}
diff --git a/pom-operator/java-sample/pom.xml b/pom-operator/java-sample/pom.xml
new file mode 100644
index 000000000..4e1578f04
--- /dev/null
+++ b/pom-operator/java-sample/pom.xml
@@ -0,0 +1,88 @@
+
+
+
+ 4.0.0
+
+ io.github.pixee.maven.sample
+ java-sample
+ 0.0.22-SNAPSHOT
+
+
+ UTF-8
+ 1.8
+ 1.8
+
+
+
+
+ io.github.pixee.maven
+ pom-operator
+ ${project.version}
+
+
+ org.slf4j
+ slf4j-api
+ 2.0.0
+
+
+ junit
+ junit
+ 4.13.2
+ test
+
+
+ org.apache.maven
+ maven-embedder
+ 3.8.6
+ provided
+
+
+
+
+
+
+
+
+ maven-clean-plugin
+ 3.1.0
+
+
+
+ maven-resources-plugin
+ 3.0.2
+
+
+ maven-compiler-plugin
+ 3.8.0
+
+
+ maven-surefire-plugin
+ 2.22.1
+
+
+ maven-jar-plugin
+ 3.0.2
+
+
+ maven-install-plugin
+ 2.5.2
+
+
+ maven-deploy-plugin
+ 2.8.2
+
+
+
+ maven-site-plugin
+ 3.7.1
+
+
+ maven-project-info-reports-plugin
+ 3.0.0
+
+
+
+
+
diff --git a/pom-operator/java-sample/src/test/java/io/github/pixee/it/POMOperatorJavaTest.java b/pom-operator/java-sample/src/test/java/io/github/pixee/it/POMOperatorJavaTest.java
new file mode 100644
index 000000000..d6267f3b4
--- /dev/null
+++ b/pom-operator/java-sample/src/test/java/io/github/pixee/it/POMOperatorJavaTest.java
@@ -0,0 +1,19 @@
+package io.github.pixee.it;
+
+import org.junit.Test;
+
+import io.github.pixee.maven.operator.ProjectModel;
+import io.github.pixee.maven.operator.Dependency;
+import io.github.pixee.maven.operator.POMOperator;
+import io.github.pixee.maven.operator.ProjectModelFactory;
+
+public class POMOperatorJavaTest {
+ @Test
+ public void testInterop() {
+ ProjectModel projectModel = ProjectModelFactory.load(POMOperatorJavaTest.class.getResource("pom.xml"))
+ .withDependency(new Dependency("org.dom4j", "dom4j", "0.0.0", null, "jar", null))
+ .build();
+
+ POMOperator.modify(projectModel);
+ }
+}
diff --git a/pom-operator/java-sample/src/test/resources/io/github/pixee/it/pom.xml b/pom-operator/java-sample/src/test/resources/io/github/pixee/it/pom.xml
new file mode 100644
index 000000000..b653bbdf6
--- /dev/null
+++ b/pom-operator/java-sample/src/test/resources/io/github/pixee/it/pom.xml
@@ -0,0 +1,148 @@
+
+
+ 4.0.0
+
+ br.com.ingenieux
+ pom-operator
+ 0.0.1-SNAPSHOT
+
+
+ UTF-8
+ 1.8
+ 1.8
+ 1.5.31
+
+
+
+
+ org.dom4j
+ dom4j
+ 2.1.3
+
+
+ jaxen
+ jaxen
+ 1.2.0
+
+
+ xerces
+ xercesImpl
+ 2.12.1
+
+
+ io.github.java-diff-utils
+ java-diff-utils
+ 4.9
+
+
+ junit
+ junit
+ 4.11
+ test
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jdk8
+ ${kotlin.version}
+
+
+ org.jetbrains.kotlin
+ kotlin-test
+ ${kotlin.version}
+ test
+
+
+
+
+
+
+
+
+ maven-clean-plugin
+ 3.1.0
+
+
+
+ maven-resources-plugin
+ 3.0.2
+
+
+ maven-compiler-plugin
+ 3.8.0
+
+
+ maven-surefire-plugin
+ 2.22.1
+
+
+ maven-jar-plugin
+ 3.0.2
+
+
+ maven-install-plugin
+ 2.5.2
+
+
+ maven-deploy-plugin
+ 2.8.2
+
+
+
+ maven-site-plugin
+ 3.7.1
+
+
+ maven-project-info-reports-plugin
+ 3.0.0
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+ ${kotlin.version}
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ test-compile
+ test-compile
+
+ test-compile
+
+
+
+
+ 1.8
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ testCompile
+ test-compile
+
+ testCompile
+
+
+
+
+
+
+
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/AbstractCommand.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/AbstractCommand.kt
new file mode 100644
index 000000000..c70105712
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/AbstractCommand.kt
@@ -0,0 +1,61 @@
+package io.github.pixee.maven.operator
+
+import io.github.pixee.maven.operator.Util.findOutIfUpgradeIsNeeded
+import io.github.pixee.maven.operator.Util.selectXPathNodes
+import io.github.pixee.maven.operator.Util.upgradeVersionNode
+import org.dom4j.Element
+import java.io.File
+
+/**
+ * Base implementation of Command - used by SimpleDependency and SimpleInsert
+ */
+abstract class AbstractCommand : Command {
+ /**
+ * Given a POM, locate its coordinates for a given dependency based on lookupExpression and figures out the upgrade
+ *
+ * TODO review this
+ */
+ protected fun handleDependency(pm: ProjectModel, lookupExpression: String): Boolean {
+ val dependencyNodes = pm.pomFile.resultPom.selectXPathNodes(lookupExpression)
+
+ if (1 == dependencyNodes.size) {
+ val versionNodes = dependencyNodes[0].selectXPathNodes("./m:version")
+
+ if (1 == versionNodes.size) {
+ val versionNode = versionNodes[0] as Element
+
+ var mustUpgrade = true
+
+ if (pm.skipIfNewer) {
+ mustUpgrade = findOutIfUpgradeIsNeeded(pm, versionNode)
+ }
+
+ if (mustUpgrade) {
+ upgradeVersionNode(pm, versionNode, pm.pomFile)
+ }
+
+ return true
+ }
+ }
+
+ return false
+ }
+
+ override fun execute(pm: ProjectModel): Boolean = false
+
+ override fun postProcess(c: ProjectModel): Boolean = false
+
+ protected fun getLocalRepositoryPath(pm: ProjectModel): File {
+ val localRepositoryPath: File = when {
+ pm.repositoryPath != null -> pm.repositoryPath
+ System.getenv("M2_REPO") != null -> File(System.getenv("M2_REPO"))
+ System.getProperty("maven.repo.local") != null -> File(System.getProperty("maven.repo.local"))
+ else -> File(
+ System.getProperty("user.home"),
+ ".m2/repository"
+ )
+ }
+
+ return localRepositoryPath
+ }
+}
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/AbstractQueryCommand.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/AbstractQueryCommand.kt
new file mode 100644
index 000000000..1ca268161
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/AbstractQueryCommand.kt
@@ -0,0 +1,206 @@
+package io.github.pixee.maven.operator
+
+import io.github.pixee.maven.operator.Util.which
+import org.apache.commons.lang3.SystemUtils
+import org.apache.maven.shared.invoker.DefaultInvocationRequest
+import org.apache.maven.shared.invoker.InvocationRequest
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import java.io.File
+import java.nio.file.Paths
+import java.util.*
+
+/**
+ * Common Base Class - Meant to be used by Simple Queries using either Invoker and/or Embedder, thus
+ * relying on dependency:tree mojo outputting into a text file - which might be cached.
+ *
+ */
+abstract class AbstractQueryCommand : AbstractCommand() {
+ /**
+ * Generates a temporary file path used to store the output of the
dependency:tree
mojo
+ *
+ * @param pomFilePath POM Original File Path
+ */
+ private fun getOutputPath(pomFilePath: File): File {
+ val basePath = pomFilePath.parentFile
+
+ val outputBasename = "output-%08X.txt".format(pomFilePath.hashCode())
+
+ val outputPath = File(basePath, outputBasename)
+
+ return outputPath
+ }
+
+ /**
+ * Given a POM URI, returns a File Object
+ *
+ * @param d POMDocument
+ */
+ protected fun getPomFilePath(d: POMDocument): File = Paths.get(d.pomPath!!.toURI()).toFile()
+
+ /**
+ * Abstract Method to extract dependencies
+ *
+ * @param outputPath Output Path to where to store the content
+ * @param pomFilePath Input Pom Path
+ * @param c Project Model
+ */
+ abstract fun extractDependencyTree(outputPath: File, pomFilePath: File, c: ProjectModel)
+
+ /**
+ * Internal Holder Variable
+ *
+ * Todo: OF COURSE IT BREAKS THE PROTOCOL
+ */
+ internal var result: Collection? = null
+
+ /**
+ * We declare the main logic here - details are made in the child classes for now
+ */
+
+ override fun execute(pm: ProjectModel): Boolean {
+ val pomFilePath = getPomFilePath(pm.pomFile)
+
+ val outputPath = getOutputPath(pomFilePath)
+
+ if (outputPath.exists()) {
+ outputPath.delete()
+ }
+
+ try {
+ extractDependencyTree(outputPath, pomFilePath, pm)
+ } catch (e: InvalidContextException) {
+ return false
+ }
+
+ this.result = extractDependencies(outputPath).values
+
+ return true
+ }
+
+ /**
+ * Given a File containing the output of the dependency:tree mojo, read its contents and parse, creating an array of dependencies
+ *
+ * About the file contents: We receive something such as this, then filter it out:
+ *
+ *
+ * br.com.ingenieux:pom-operator:jar:0.0.1-SNAPSHOT
+ * +- xerces:xercesImpl:jar:2.12.1:compile
+ * | \- xml-apis:xml-apis:jar:1.4.01:compile
+ * \- org.jetbrains.kotlin:kotlin-test:jar:1.5.31:test
+ *
+ *
+ * @param outputPath file to read
+ */
+ protected fun extractDependencies(outputPath: File) = outputPath.readLines().drop(1).map {
+ it.trim(*"+-|\\ ".toCharArray())
+ }.map {
+ it to it.split(':')
+ }.associate { (line, elements) ->
+ val (groupId, artifactId, packaging, version, scope) = elements
+
+ line to Dependency(
+ groupId = groupId,
+ artifactId = artifactId,
+ version = version,
+ packaging = packaging,
+ scope = scope
+ )
+ }
+
+ protected fun buildInvocationRequest(
+ outputPath: File,
+ pomFilePath: File,
+ c: ProjectModel
+ ): InvocationRequest {
+ val props = Properties(System.getProperties()).apply {
+ setProperty("outputFile", outputPath.absolutePath)
+
+ val localRepositoryPath = getLocalRepositoryPath(c).absolutePath
+
+ setProperty("maven.repo.local", localRepositoryPath)
+ }
+
+ val request: InvocationRequest = DefaultInvocationRequest().apply {
+ findMaven(this)
+
+ pomFile = pomFilePath
+
+ isShellEnvironmentInherited = true
+
+ isNoTransferProgress = true
+ isBatchMode = true
+ isRecursive = false
+ profiles = c.activeProfiles.toList()
+ isDebug = true
+
+ isOffline = c.offline
+
+ properties = props
+
+ goals = listOf(DEPENDENCY_TREE_MOJO_REFERENCE)
+ }
+
+ return request
+ }
+
+ /**
+ * Locates where Maven is at - HOME var and main launcher script.
+ *
+ * @param invocationRequest InvocationRequest to be filled up
+ */
+ private fun findMaven(invocationRequest: InvocationRequest) {
+ /*
+ * Step 1: Locate Maven Home
+ */
+ val m2homeEnvVar = System.getenv("M2_HOME")
+
+ if (null != m2homeEnvVar) {
+ val m2HomeDir = File(m2homeEnvVar)
+
+ if (m2HomeDir.isDirectory)
+ invocationRequest.mavenHome = m2HomeDir
+ }
+
+ /**
+ * Step 1.1: Try to guess if thats the case
+ */
+ if (invocationRequest.mavenHome == null) {
+ val inferredHome = File(SystemUtils.getUserHome(), ".m2")
+
+ if (!(inferredHome.exists() && inferredHome.isDirectory)) {
+ LOGGER.warn(
+ "Inferred User Home - which does not exist or not a directory: {}",
+ inferredHome
+ )
+ }
+
+ invocationRequest.mavenHome = inferredHome
+ }
+
+ /**
+ * Step 2: Find Maven Executable given the operating system and PATH variable contents
+ */
+ val foundExecutable = listOf("mvn", "mvnw").map { which(it) }.firstOrNull()
+
+ if (null != foundExecutable) {
+ invocationRequest.mavenExecutable = foundExecutable
+
+ return
+ }
+
+ throw IllegalStateException("Missing Maven Home / Executable")
+ }
+
+ companion object {
+ /**
+ * Mojo Reference
+ */
+ const val DEPENDENCY_TREE_MOJO_REFERENCE =
+ "org.apache.maven.plugins:maven-dependency-plugin:3.3.0:tree"
+
+ val LOGGER: Logger = LoggerFactory.getLogger(AbstractQueryCommand::class.java)
+ }
+
+ override fun postProcess(c: ProjectModel): Boolean = false
+}
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/Chain.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/Chain.kt
new file mode 100644
index 000000000..67bd4124f
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/Chain.kt
@@ -0,0 +1,114 @@
+package io.github.pixee.maven.operator
+
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+/**
+ * Implements a Chain of Responsibility Pattern
+ *
+ * @constructor commands: Commands to Use
+ */
+class Chain(vararg commands: Command) {
+ /**
+ * Internal ArrayList of the Commands
+ */
+ internal val commandList: MutableList = ArrayList(commands.toList())
+
+ /**
+ * Executes the Commands in the Chain of Responsibility
+ *
+ * @param c ProjectModel (context)
+ * @return Boolean if successful
+ */
+ fun execute(c: ProjectModel): Boolean {
+ var done = false
+ val listIterator = commandList.listIterator()
+
+ while ((!done) && listIterator.hasNext()) {
+ val nextCommand = listIterator.next()
+
+ done = nextCommand.execute(c)
+
+ if (done) {
+ if (c.queryType == QueryType.NONE && (nextCommand !is SupportCommand)) {
+ c.modifiedByCommand = true
+ }
+
+ break
+ }
+ }
+
+ val result = done
+
+ /**
+ * Goes Reverse Order applying the filter pattern
+ */
+
+ while (listIterator.previousIndex() > 0) {
+ val nextCommand = listIterator.previous()
+
+ done = nextCommand.postProcess(c)
+
+ if (done)
+ break
+ }
+
+ return result
+ }
+
+ companion object {
+ private val LOGGER: Logger = LoggerFactory.getLogger(Chain::class.java)
+
+ /**
+ * Some classes won't have all available dependencies on the classpath during runtime
+ * for this reason we'll use Class.forName
and report issues creating
+ */
+ val AVAILABLE_QUERY_COMMANDS = listOf(
+ QueryType.SAFE to "QueryByResolver",
+ QueryType.SAFE to "QueryByParsing",
+ QueryType.UNSAFE to "QueryByEmbedder",
+ QueryType.UNSAFE to "QueryByInvoker",
+ )
+
+ /**
+ * Returns a Pre-Configured Chain with the Defaults for Modifying a POM
+ */
+ fun createForModify() =
+ Chain(
+ CheckDependencyPresent,
+ CheckParentPackaging,
+ FormatCommand(),
+ DiscardFormatCommand(),
+ CompositeDependencyManagement(),
+ SimpleUpgrade,
+ SimpleDependencyManagement,
+ SimpleInsert
+ )
+
+ /**
+ * returns a pre-configured chain with the defaults for Querying
+ */
+ fun createForQuery(queryType: QueryType = QueryType.SAFE): Chain {
+ val filteredCommands: List = AVAILABLE_QUERY_COMMANDS
+ .filter { it.first == queryType }.mapNotNull {
+ val commandClassName = "io.github.pixee.maven.operator.${it.second}"
+
+ try {
+ Class.forName(commandClassName).newInstance() as Command
+ } catch (e: Throwable) {
+ LOGGER.warn("Creating class '{}': ", commandClassName, e)
+
+ null
+ }
+ }
+ .toList()
+
+ val commands : List = listOf(CHECK_PARENT_DIR_COMMAND) + filteredCommands
+
+ if (commands.isEmpty())
+ throw IllegalStateException("Unable to load any available strategy for ${queryType.name}")
+
+ return Chain(*commands.toTypedArray())
+ }
+ }
+}
\ No newline at end of file
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/CheckDependencyPresent.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/CheckDependencyPresent.kt
new file mode 100644
index 000000000..777f76d39
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/CheckDependencyPresent.kt
@@ -0,0 +1,16 @@
+package io.github.pixee.maven.operator
+
+/**
+ * Guard Command Singleton use to validate required parameters
+ */
+val CheckDependencyPresent = object : AbstractCommand() {
+ override fun execute(pm: ProjectModel): Boolean {
+ /**
+ * CheckDependencyPresent requires a Dependency to be Present
+ */
+ if (null == pm.dependency)
+ throw MissingDependencyException("Dependency must be present for modify")
+
+ return false
+ }
+}
\ No newline at end of file
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/CheckLocalRepositoryDirCommand.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/CheckLocalRepositoryDirCommand.kt
new file mode 100644
index 000000000..c25591c3c
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/CheckLocalRepositoryDirCommand.kt
@@ -0,0 +1,18 @@
+package io.github.pixee.maven.operator
+
+import java.io.File
+
+val CHECK_PARENT_DIR_COMMAND = object : AbstractQueryCommand() {
+ override fun extractDependencyTree(outputPath: File, pomFilePath: File, c: ProjectModel) =
+ throw InvalidContextException()
+
+ override fun execute(c: ProjectModel): Boolean {
+ val localRepositoryPath = getLocalRepositoryPath(c)
+
+ if (!localRepositoryPath.exists()) {
+ localRepositoryPath.mkdirs()
+ }
+
+ return false
+ }
+}
\ No newline at end of file
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/CheckParentPackaging.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/CheckParentPackaging.kt
new file mode 100644
index 000000000..7a44cf14d
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/CheckParentPackaging.kt
@@ -0,0 +1,54 @@
+package io.github.pixee.maven.operator
+
+import io.github.pixee.maven.operator.Util.selectXPathNodes
+import org.dom4j.Element
+import org.dom4j.Text
+
+/**
+ * Guard Command Singleton use to validate required parameters
+ */
+val CheckParentPackaging = object : AbstractCommand() {
+ fun packagingTypePredicate(d: POMDocument, packagingType: String): Boolean {
+ val elementText =
+ d.pomDocument.rootElement.selectXPathNodes("/m:project/m:packaging/text()")
+ .firstOrNull()
+
+ if (elementText is Text) {
+ return elementText.text.equals(packagingType)
+ }
+
+ return false
+ }
+
+ override fun execute(pm: ProjectModel): Boolean {
+ val wrongParentPoms = pm.parentPomFiles.filterNot { packagingTypePredicate(it, "pom") }
+
+ if (wrongParentPoms.isNotEmpty()) {
+ throw WrongDependencyTypeException("wrong packaging type for parentPom")
+ }
+
+ if (pm.parentPomFiles.isNotEmpty()) {
+ // check main pom file has a inheritance to one of the members listed
+ if (!hasValidParentAndPackaging(pm.pomFile)) {
+ throw WrongDependencyTypeException("invalid parent/packaging combo for main pomfile")
+ }
+ }
+
+ // todo: test a->b->c
+
+ return false
+ }
+
+ private fun hasValidParentAndPackaging(pomFile: POMDocument): Boolean {
+ val parentNode = pomFile.pomDocument.rootElement.selectXPathNodes("/m:project/m:parent")
+ .firstOrNull() as Element? ?: return false
+
+ val packagingText =
+ (pomFile.pomDocument.rootElement.selectXPathNodes("/m:project/m:packaging/text()")
+ .firstOrNull() as Text?)?.text ?: "jar"
+
+ @Suppress("UnnecessaryVariable") val validPackagingType = packagingText.endsWith("ar")
+
+ return validPackagingType
+ }
+}
\ No newline at end of file
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/Command.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/Command.kt
new file mode 100644
index 000000000..776ae41b1
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/Command.kt
@@ -0,0 +1,19 @@
+package io.github.pixee.maven.operator
+
+/**
+ * Represents a Command in a Chain of Responsibility Pattern
+ */
+interface Command {
+ /**
+ * Given a context, performs an operation
+ *
+ * @param pm Context (Project Model) to use
+ * @return true if the execution was successful *AND* the chain must end
+ */
+ fun execute(pm: ProjectModel): Boolean
+
+ /**
+ * Post Processing, implementing a Filter Pattern
+ */
+ fun postProcess(c: ProjectModel): Boolean
+}
\ No newline at end of file
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/CompositeDependencyManagement.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/CompositeDependencyManagement.kt
new file mode 100644
index 000000000..aa712ca95
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/CompositeDependencyManagement.kt
@@ -0,0 +1,125 @@
+package io.github.pixee.maven.operator
+
+import io.github.pixee.maven.operator.Util.addIndentedElement
+import io.github.pixee.maven.operator.Util.selectXPathNodes
+import org.dom4j.Element
+import java.lang.IllegalStateException
+
+class CompositeDependencyManagement : AbstractCommand() {
+ override fun execute(pm: ProjectModel): Boolean {
+ /**
+ * Abort if not multi-pom
+ */
+ if (pm.parentPomFiles.isEmpty()) {
+ return false
+ }
+
+ var result = false
+
+ /**
+ * TODO: Make it configurable / clear WHERE one should change it
+ */
+ val parentPomFile = pm.parentPomFiles.last()
+
+ // add dependencyManagement
+
+ val dependencyManagementElement =
+ if (parentPomFile.resultPom.rootElement.elements("dependencyManagement").isEmpty()) {
+ parentPomFile.resultPom.rootElement.addIndentedElement(
+ parentPomFile,
+ "dependencyManagement"
+ )
+ } else {
+ parentPomFile.resultPom.rootElement.element("dependencyManagement")
+ }
+
+ val newDependencyManagementElement = modifyDependency(
+ parentPomFile,
+ Util.buildLookupExpressionForDependencyManagement(pm.dependency!!),
+ pm,
+ dependencyManagementElement,
+ dependencyManagementNode = true,
+ )
+
+ if (pm.useProperties) {
+ val newVersionNode =
+ newDependencyManagementElement?.addIndentedElement(parentPomFile, "version")
+ ?: throw IllegalStateException("newDependencyManagementElement is missing")
+
+ val whereToUpgradeVersionProperty = parentPomFile
+
+ Util.upgradeVersionNode(pm, newVersionNode, whereToUpgradeVersionProperty)
+ }
+
+ // add dependency to pom - sans version
+ modifyDependency(
+ pm.pomFile,
+ Util.buildLookupExpressionForDependency(pm.dependency!!),
+ pm,
+ pm.pomFile.resultPom.rootElement,
+ dependencyManagementNode = false,
+ )
+
+ if (!result) {
+ result = pm.pomFile.dirty
+ }
+
+ return result
+ }
+
+ private fun modifyDependency(
+ pomFileToModify: POMDocument,
+ lookupExpressionForDependency: String,
+ c: ProjectModel,
+ parentElement: Element,
+ dependencyManagementNode: Boolean,
+ ): Element? {
+ val dependencyNodes =
+ pomFileToModify.resultPom.selectXPathNodes(lookupExpressionForDependency)
+
+ if (1 == dependencyNodes.size) {
+ val versionNodes = dependencyNodes[0].selectXPathNodes("./m:version")
+
+ if (1 == versionNodes.size) {
+ val versionNode = versionNodes.first()
+
+ versionNode.parent.content().remove(versionNode)
+
+ pomFileToModify.dirty = true
+ }
+
+ return dependencyNodes[0] as Element
+ } else {
+ val dependenciesNode: Element =
+ if (null != parentElement.element("dependencies")) {
+ parentElement.element("dependencies")
+ } else {
+ parentElement.addIndentedElement(
+ pomFileToModify,
+ "dependencies"
+ )
+ }
+
+ val dependencyNode: Element =
+ dependenciesNode.addIndentedElement(pomFileToModify, "dependency")
+
+ dependencyNode.addIndentedElement(pomFileToModify, "groupId").text =
+ c.dependency!!.groupId
+ dependencyNode.addIndentedElement(pomFileToModify, "artifactId").text =
+ c.dependency!!.artifactId
+
+ if (dependencyManagementNode) {
+ if (!c.useProperties) {
+ dependencyNode.addIndentedElement(pomFileToModify, "version").text =
+ c.dependency!!.version!!
+ }
+ }
+
+ pomFileToModify.dirty = true
+
+ return dependencyNode
+ }
+
+ return null
+ }
+}
\ No newline at end of file
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/Dependency.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/Dependency.kt
new file mode 100644
index 000000000..b420b1c62
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/Dependency.kt
@@ -0,0 +1,30 @@
+package io.github.pixee.maven.operator
+
+/**
+ * Represents a Dependency
+ */
+data class Dependency(
+ val groupId: String,
+ val artifactId: String,
+ val version: String? = null,
+ val classifier: String? = null,
+ val packaging: String? = "jar",
+ val scope: String? = "compile",
+) {
+ override fun toString(): String {
+ return listOf(groupId, artifactId, packaging, version).joinToString(":")
+ }
+ /**
+ * Given a string, parses - and creates - a new dependency Object
+ */
+ companion object {
+ fun fromString(str: String): Dependency {
+ val elements = str.split(":")
+
+ if (elements.size < 3)
+ throw IllegalStateException("Give me at least 3 elements")
+
+ return Dependency(elements[0], elements[1], elements[2])
+ }
+ }
+}
\ No newline at end of file
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/DiscardFormatCommand.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/DiscardFormatCommand.kt
new file mode 100644
index 000000000..acb8dc87b
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/DiscardFormatCommand.kt
@@ -0,0 +1,41 @@
+package io.github.pixee.maven.operator
+
+import org.xmlunit.builder.DiffBuilder
+import org.xmlunit.builder.Input
+
+/**
+ * Command Class to Short-Circuit/Discard Processing when no pom changes were made
+ */
+class DiscardFormatCommand : AbstractCommand() {
+ override fun postProcess(pm: ProjectModel): Boolean {
+ var mustSkip = false
+
+ for (pomFile in pm.allPomFiles) {
+ val originalDoc = Input.fromString(String(pomFile.originalPom)).build()
+ val modifiedDoc = Input.fromString(pomFile.resultPom.asXML()).build()
+
+ val diff = DiffBuilder.compare(originalDoc).withTest(modifiedDoc)
+ .ignoreWhitespace()
+ .ignoreComments()
+ .ignoreElementContentWhitespace()
+ .checkForSimilar()
+ .build()
+
+ val hasDifferences = diff.hasDifferences()
+
+ if (!(pm.modifiedByCommand || hasDifferences)) {
+ pomFile.resultPomBytes = pomFile.originalPom
+
+ mustSkip = true
+ }
+ }
+
+ /**
+ * Triggers early abandonment
+ */
+ if (mustSkip)
+ return true
+
+ return super.postProcess(pm)
+ }
+}
\ No newline at end of file
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/EmbedderFacade.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/EmbedderFacade.kt
new file mode 100644
index 000000000..baa5b33c8
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/EmbedderFacade.kt
@@ -0,0 +1,146 @@
+package io.github.pixee.maven.operator
+
+import org.apache.maven.model.building.*
+import org.apache.maven.project.ProjectModelResolver
+import org.apache.maven.repository.internal.MavenRepositorySystemUtils
+import org.eclipse.aether.DefaultRepositorySystemSession
+import org.eclipse.aether.RepositorySystem
+import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory
+import org.eclipse.aether.impl.DefaultServiceLocator
+import org.eclipse.aether.internal.impl.DefaultRemoteRepositoryManager
+import org.eclipse.aether.repository.LocalRepository
+import org.eclipse.aether.repository.RemoteRepository
+import org.eclipse.aether.spi.connector.RepositoryConnectorFactory
+import org.eclipse.aether.spi.connector.transport.TransporterFactory
+import org.eclipse.aether.transport.file.FileTransporterFactory
+import org.eclipse.aether.transport.http.HttpTransporterFactory
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import java.io.File
+
+data class EmbedderFacadeRequest(
+ val offline: Boolean,
+ val localRepositoryPath: File? = null,
+ val pomFile: File?,
+ val activeProfileIds: Collection = emptyList(),
+ val inactiveProfileIds: Collection = emptyList(),
+)
+
+data class EmbedderFacadeResponse(
+ val modelBuildingResult: ModelBuildingResult,
+ val session: DefaultRepositorySystemSession?,
+ val repositorySystem: RepositorySystem?,
+ val remoteRepositories: List = emptyList(),
+)
+
+object EmbedderFacade {
+ // Embedder Impl
+
+ fun invokeEmbedder(req: EmbedderFacadeRequest): EmbedderFacadeResponse {
+ val localRepoPath: File = when {
+ req.localRepositoryPath != null -> req.localRepositoryPath
+ System.getenv("M2_REPO") != null -> File(System.getenv("M2_REPO"))
+ System.getProperty("maven.repo.local") != null -> File(System.getProperty("maven.repo.local"))
+ else -> File(
+ System.getProperty("user.home"),
+ ".m2/repository"
+ )
+ }
+
+ val localRepository = LocalRepository(localRepoPath.absolutePath)
+
+ val locator: DefaultServiceLocator =
+ MavenRepositorySystemUtils.newServiceLocator()
+
+ locator.addService(
+ RepositoryConnectorFactory::class.java,
+ BasicRepositoryConnectorFactory::class.java
+ )
+
+ locator.addService(
+ TransporterFactory::class.java,
+ FileTransporterFactory::class.java
+ )
+
+ locator.addService(
+ TransporterFactory::class.java,
+ HttpTransporterFactory::class.java
+ )
+
+ locator.setErrorHandler(object :
+ DefaultServiceLocator.ErrorHandler() {
+ override fun serviceCreationFailed(
+ type: Class<*>?,
+ impl: Class<*>?,
+ exception: Throwable
+ ) {
+ LOGGER.error(
+ "Service creation failed for {} with implementation {}",
+ type, impl, exception
+ )
+ }
+ })
+
+ val repositorySystem = locator.getService(RepositorySystem::class.java)
+
+ val session = MavenRepositorySystemUtils.newSession()
+
+ session.localRepositoryManager =
+ repositorySystem.newLocalRepositoryManager(session, localRepository)
+
+ session.setOffline(req.offline)
+
+ val modelBuilder = DefaultModelBuilderFactory().newInstance()
+
+ val repositoryManager = DefaultRemoteRepositoryManager()
+
+ val remoteRepository =
+ RemoteRepository.Builder("central", "default", "https://repo.maven.apache.org/maven2/")
+ .build()
+
+ val remoteRepositories = if (req.offline) {
+ emptyList()
+ } else {
+ listOf(remoteRepository)
+ }
+
+ val modelBuildingRequest = DefaultModelBuildingRequest().apply {
+ this.userProperties = System.getProperties()
+ this.systemProperties = System.getProperties()
+ this.pomFile = req.pomFile!!
+
+ this.isProcessPlugins = false
+
+ this.modelSource = FileModelSource(pomFile)
+
+ val modelResolver = ProjectModelResolver(
+ session,
+ null,
+ repositorySystem,
+ repositoryManager,
+ remoteRepositories,
+ null,
+ null
+ )
+
+ this.modelResolver = modelResolver
+ }
+
+ val modelBuildingResult: ModelBuildingResult = try {
+ modelBuilder.build(modelBuildingRequest)
+ } catch (e: ModelBuildingException) {
+ LOGGER.warn("Oops: ", e)
+
+ throw e
+ }
+
+ return EmbedderFacadeResponse(
+ modelBuildingResult = modelBuildingResult,
+ session = session,
+ repositorySystem = repositorySystem,
+ remoteRepositories = remoteRepositories,
+ )
+ }
+
+ private val LOGGER: Logger = LoggerFactory.getLogger(EmbedderFacade::class.java)
+}
\ No newline at end of file
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/FormatCommand.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/FormatCommand.kt
new file mode 100644
index 000000000..38fab6f95
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/FormatCommand.kt
@@ -0,0 +1,341 @@
+package io.github.pixee.maven.operator
+
+import org.apache.commons.lang3.StringUtils
+import org.mozilla.universalchardet.UniversalDetector
+import java.io.StringWriter
+import java.nio.charset.Charset
+import java.util.*
+import javax.xml.stream.XMLInputFactory
+import javax.xml.stream.XMLOutputFactory
+import javax.xml.stream.events.Characters
+import javax.xml.stream.events.EndElement
+import javax.xml.stream.events.StartDocument
+import javax.xml.stream.events.StartElement
+
+/**
+ * Data Class used to keep track of matches (ranges, content, referring tag name)
+ */
+data class MatchData(
+ val range: IntRange,
+ val content: String,
+ val elementName: String
+)
+
+/**
+ * This Command handles Formatting - particularly storing the original document preamble (the Processing Instruction and the first XML Element contents),
+ * which are the only ones which are tricky to format (due to element and its attributes being freeform - thus formatting lost when serializing the DOM
+ * and the PI being completely optional for the POM Document)
+ */
+class FormatCommand : AbstractCommand() {
+ /**
+ * StAX InputFactory
+ */
+ private val inputFactory = XMLInputFactory.newInstance()
+
+ /**
+ * StAX OutputFactory
+ */
+ private val outputFactory = XMLOutputFactory.newInstance()
+
+ override fun execute(pm: ProjectModel): Boolean {
+ for (pomFile in pm.allPomFiles) {
+ parseXmlAndCharset(pomFile)
+
+ pomFile.endl = parseLineEndings(pomFile)
+ pomFile.indent = guessIndent(pomFile)
+ }
+
+ return super.execute(pm)
+ }
+
+ /**
+ * This one is quite fun yet important. Let me explain:
+ *
+ * The DOM doesn't track records if empty elements are either `` or ``. Therefore we need to scan all ocurrences of
+ * singleton elements.
+ *
+ * Therefore we use a bitSet to keep track of each element and offset, scanning it forward
+ * when serializing we pick backwards and rewrite tags accordingly
+ *
+ * @param doc Raw Document Bytes
+ * @see RE_EMPTY_ELEMENT
+ * @return bitSet of
+ *
+ */
+ private fun elementBitSet(doc: ByteArray): BitSet {
+ val result = BitSet()
+ val eventReader = inputFactory.createXMLEventReader(doc.inputStream())
+ val eventContent = StringWriter()
+ val xmlEventWriter = outputFactory.createXMLEventWriter(eventContent)
+
+ while (eventReader.hasNext()) {
+ val next = eventReader.nextEvent()
+
+ if (next is StartElement || next is EndElement) {
+ val startIndex = next.location.characterOffset
+
+ eventContent.buffer.setLength(0)
+
+ xmlEventWriter.add(next)
+ xmlEventWriter.flush()
+
+ val endIndex = startIndex + eventContent.buffer.length
+
+ result.set(startIndex, startIndex + endIndex)
+ }
+ }
+
+ return result
+ }
+
+ /**
+ * Returns a reverse-ordered list of all the single element matches from the pom document
+ * raw string
+ *
+ * this is important so we can mix and match offsets and apply formatting accordingly
+ *
+ * @param xmlDocumentString Rendered POM Document Contents (string-formatted)
+ * @return map of (index, matchData object) reverse ordered
+ */
+ private fun findSingleElementMatchesFrom(xmlDocumentString: String) =
+ RE_EMPTY_ELEMENT.findAll(xmlDocumentString).map {
+ it.range.first to MatchData(
+ range = it.range,
+ content = it.value,
+ elementName = ((it.groups[1]?.value ?: it.groups[2]?.value)!!)
+ )
+ }.sortedByDescending { it.first }.toMap(LinkedHashMap())
+
+ /**
+ * Guesses the indent character (spaces / tabs) and length from the original document
+ * formatting settings
+ *
+ * @param pomFile (project model) where it takes its input pom
+ * @return indent string
+ */
+ private fun guessIndent(pomFile: POMDocument): String {
+ val eventReader = inputFactory.createXMLEventReader(pomFile.originalPom.inputStream())
+
+ val freqMap: MutableMap = mutableMapOf()
+ val charFreqMap: MutableMap = mutableMapOf()
+
+ /**
+ * Parse, while grabbing whitespace sequences and examining it
+ */
+ while (eventReader.hasNext()) {
+ val event = eventReader.nextEvent()
+
+ if (event is Characters) {
+ if (StringUtils.isWhitespace(event.asCharacters().data)) {
+ val patterns = event.asCharacters().data.split(*LINE_ENDINGS.toTypedArray())
+
+ /**
+ * Updates space / character frequencies found
+ */
+ val blankPatterns = patterns
+ .filter { it.isNotEmpty() }
+ .filter { StringUtils.isAllBlank(it) }
+
+ blankPatterns
+ .map { it to it.length }
+ .forEach {
+ freqMap.merge(it.second, 1) { a, b -> a + b }
+ }
+
+ blankPatterns.map { it[0] }
+ .forEach {
+ charFreqMap.merge(it, 1) { a, b ->
+ a + b
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Assign most frequent indent char
+ */
+ val indentCharacter: Char = charFreqMap.entries.maxBy { it.value }.key
+
+ /**
+ * Casts as a String
+ */
+ val indentcharacterAsString = String(charArrayOf(indentCharacter))
+
+ /**
+ * Picks the length
+ */
+ val indentLength = freqMap.entries.minBy { it.key }.key
+
+ /**
+ * Builds the standard indent string (length vs char)
+ */
+ val indentString = StringUtils.repeat(indentcharacterAsString, indentLength)
+
+ /**
+ * Returns it
+ */
+ return indentString
+ }
+
+ private fun parseLineEndings(pomFile: POMDocument): String {
+ val str = String(pomFile.originalPom.inputStream().readBytes(), pomFile.charset)
+
+ return LINE_ENDINGS.associateWith { str.split(it).size }
+ .maxBy { it.value }
+ .key
+ }
+
+ private fun parseXmlAndCharset(pomFile: POMDocument) {
+ /**
+ * Performs a StAX Parsing to Grab the first element
+ */
+ val eventReader = inputFactory.createXMLEventReader(pomFile.originalPom.inputStream())
+
+ var charset: Charset? = null
+
+ /**
+ * Parse, while grabbing its preamble and encoding
+ */
+ while (true) {
+ val event = eventReader.nextEvent()
+
+ if (event.isStartDocument && (event as StartDocument).encodingSet()) {
+ /**
+ * Processing Instruction Found - Store its Character Encoding
+ */
+ charset = Charset.forName(event.characterEncodingScheme)
+ } else if (event.isEndElement) {
+ /**
+ * First End of Element ("Tag") found - store its offset
+ */
+ val endElementEvent = (event as EndElement)
+
+ val offset = endElementEvent.location.characterOffset
+
+ pomFile.preamble =
+ pomFile.originalPom.toString(pomFile.charset).substring(0, offset)
+
+ break
+ }
+
+ if (!eventReader.hasNext())
+ throw IllegalStateException("Couldn't find document start")
+ }
+
+ if (null == charset) {
+ val detectedCharsetName =
+ UniversalDetector.detectCharset(pomFile.originalPom.inputStream())
+
+ charset = Charset.forName(detectedCharsetName)
+ }
+
+ pomFile.charset = charset!!
+
+ val lastLine = String(pomFile.originalPom, pomFile.charset)
+
+ val lastLineTrimmed = lastLine.trimEnd()
+
+ pomFile.suffix = lastLine.substring(lastLineTrimmed.length)
+ }
+
+ /**
+ * When doing the opposite, render the XML using the optionally supplied encoding (defaults to UTF8 obviously)
+ * but apply the original formatting as well
+ */
+ override fun postProcess(pm: ProjectModel): Boolean {
+ for (pomFile in pm.allPomFiles) {
+ /**
+ * Serializes it back
+ */
+ val content = serializePomFile(pomFile)
+
+ pomFile.resultPomBytes = content
+ }
+
+ return super.postProcess(pm)
+ }
+
+ /**
+ * Serialize a POM Document
+ *
+ * @param pom pom document
+ * @return bytes for the pom document
+ */
+ private fun serializePomFile(pom: POMDocument): ByteArray {
+ // Generate a String representation. We'll need to patch it up and apply back
+ // differences we recored previously on the pom (see the pom member variables)
+ var xmlRepresentation = pom.resultPom.asXML().toString()
+
+ val originalElementMap = elementBitSet(pom.originalPom)
+ val targetElementMap = elementBitSet(xmlRepresentation.toByteArray())
+
+ // Let's find out the original empty elements from the original pom and store into a stack
+ val elementsToReplace: MutableList = ArrayList().apply {
+ val matches =
+ findSingleElementMatchesFrom(pom.originalPom.toString(pom.charset)).values
+
+ val filteredMatches = matches.filter { originalElementMap[it.range.first] }
+
+ this.addAll(filteredMatches)
+ }
+
+ // Lets to the replacements backwards on the existing, current pom
+ val emptyElements = findSingleElementMatchesFrom(xmlRepresentation)
+ .filter { targetElementMap[it.value.range.first] }
+
+ emptyElements.forEach { (_, match) ->
+ val nextMatch = elementsToReplace.removeFirst()
+
+ xmlRepresentation = xmlRepresentation.replaceRange(match.range, nextMatch.content)
+ }
+
+ /**
+ * We might need to replace the beginning of the POM with the same content
+ * from the very beginning
+ *
+ * Grab the same initial offset from the formatted element like we did
+ */
+ val inputFactory = XMLInputFactory.newInstance()
+ val eventReader = inputFactory.createXMLEventReader(
+ xmlRepresentation.toByteArray(pom.charset).inputStream()
+ )
+
+ while (true) {
+ val event = eventReader.nextEvent()
+
+ if (event.isEndElement) {
+ /**
+ * Apply the formatting and tweak its XML Representation
+ */
+ val endElementEvent = (event as EndElement)
+
+ val offset = endElementEvent.location.characterOffset
+
+ xmlRepresentation =
+ pom.preamble + xmlRepresentation.substring(offset) + pom.suffix
+
+ break
+ }
+
+ /**
+ * This code shouldn't be unreachable at all
+ */
+ if (!eventReader.hasNext())
+ throw IllegalStateException("Couldn't find document start")
+ }
+
+ /**
+ * Serializes it back from (string to ByteArray)
+ */
+ val serializedContent = xmlRepresentation.toByteArray(pom.charset)
+
+ return serializedContent
+ }
+
+ companion object {
+ val LINE_ENDINGS = setOf("\r\n", "\n", "\r")
+
+ val RE_EMPTY_ELEMENT = Regex("""<(\p{Alnum}+)>\1>|<(\p{Alnum}+)\s*/>""")
+ }
+}
\ No newline at end of file
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/InvalidContextException.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/InvalidContextException.kt
new file mode 100644
index 000000000..12817b9ce
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/InvalidContextException.kt
@@ -0,0 +1,6 @@
+package io.github.pixee.maven.operator
+
+/**
+ * This is an exception to tag when the output file couldn't be generated - perhaps due a missing or incompatible maven installation
+ */
+internal class InvalidContextException : RuntimeException()
\ No newline at end of file
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/InvalidPathException.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/InvalidPathException.kt
new file mode 100644
index 000000000..b9fe110e1
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/InvalidPathException.kt
@@ -0,0 +1,10 @@
+package io.github.pixee.maven.operator
+
+import java.io.File
+import java.io.IOException
+
+class InvalidPathException(
+ val parentPath: File,
+ val relativePath: String,
+ val loop: Boolean = false
+) : IOException("Invalid Relative Path $relativePath (from ${parentPath.absolutePath}) (loops? ${loop})")
\ No newline at end of file
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/MissingDependencyException.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/MissingDependencyException.kt
new file mode 100644
index 000000000..757c89f14
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/MissingDependencyException.kt
@@ -0,0 +1,3 @@
+package io.github.pixee.maven.operator
+
+class MissingDependencyException(message: String?) : RuntimeException(message)
\ No newline at end of file
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/POMDocument.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/POMDocument.kt
new file mode 100644
index 000000000..4afebb2ac
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/POMDocument.kt
@@ -0,0 +1,59 @@
+package io.github.pixee.maven.operator
+
+import org.dom4j.Document
+import java.io.File
+import java.net.URL
+import java.nio.charset.Charset
+
+/**
+ * Data Class to Keep track of an entire POM File, including:
+ *
+ * Path (pomPath)
+ *
+ * DOM Contents (pomDocument) - original
+ * DOM Contents (resultPom) - modified
+ *
+ * Charset (ditto)
+ * Indent (ditto)
+ * Preamble (ditto)
+ * Suffix (ditto)
+ * Line Endings (endl)
+ *
+ * Original Content (originalPom)
+ * Modified Content (resultPomBytes)
+ */
+@Suppress("ArrayInDataClass")
+data class POMDocument(
+ val originalPom: ByteArray,
+ val pomPath: URL?,
+ val pomDocument: Document,
+ var charset: Charset = Charset.defaultCharset(),
+ var endl: String = "\n",
+ var indent: String = " ",
+ var resultPomBytes: ByteArray = byteArrayOf(),
+
+ /**
+ * Preamble Contents are stored here
+ */
+ var preamble: String = "",
+
+ /**
+ * Afterword - if needed
+ */
+ var suffix: String = "",
+) {
+ internal val file: File get() = File(this.pomPath!!.toURI())
+
+ val resultPom: Document = pomDocument.clone() as Document
+
+ var dirty: Boolean = false
+
+ override fun toString(): String {
+ return if (null == this.pomPath) {
+ "missing"
+ } else {
+ ("[POMDocument @ " + this.pomPath.toString() + "]")
+ }
+ }
+}
+
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/POMDocumentFactory.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/POMDocumentFactory.kt
new file mode 100644
index 000000000..4f5a74bce
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/POMDocumentFactory.kt
@@ -0,0 +1,36 @@
+package io.github.pixee.maven.operator
+
+import org.apache.commons.io.IOUtils
+import org.dom4j.io.SAXReader
+import java.io.File
+import java.io.InputStream
+import java.net.URL
+
+/**
+ * Factory for a POMDocument
+ */
+object POMDocumentFactory {
+ @JvmStatic
+ fun load(`is`: InputStream): POMDocument {
+ val originalPom: ByteArray = IOUtils.toByteArray(`is`)
+ val pomDocument = SAXReader().read(originalPom.inputStream())!!
+
+ return POMDocument(originalPom = originalPom, pomDocument = pomDocument, pomPath = null)
+ }
+
+ @JvmStatic
+ fun load(f: File) =
+ load(f.toURI().toURL())
+
+ @JvmStatic
+ fun load(url: URL): POMDocument {
+ val originalPom: ByteArray = IOUtils.toByteArray(url.openStream())
+
+ val saxReader = SAXReader()
+
+ val pomDocument = saxReader.read(originalPom.inputStream())
+
+ return POMDocument(originalPom = originalPom, pomPath = url, pomDocument = pomDocument)
+ }
+
+}
\ No newline at end of file
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/POMOperator.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/POMOperator.kt
new file mode 100644
index 000000000..85c5e8578
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/POMOperator.kt
@@ -0,0 +1,52 @@
+package io.github.pixee.maven.operator
+
+
+/**
+ * Façade for the POM Operator
+ */
+object POMOperator {
+ /**
+ * Bump a Dependency Version on a POM
+ *
+ * @param projectModel Project Model (Context) class
+ */
+ @JvmStatic
+ fun modify(projectModel: ProjectModel) = Chain.createForModify().execute(projectModel)
+
+ /**
+ * Public API - Query for all the artifacts referenced inside a POM File
+ *
+ * @param projectModel Project Model (Context) Class
+ */
+ @JvmStatic
+ fun queryDependency(
+ projectModel: ProjectModel
+ ) = queryDependency(projectModel, emptyList())
+
+ /**
+ * Internal Use (package-wide) - Query for all the artifacts mentioned on a POM
+ *
+ * @param projectModel Project Model (Context) class
+ * @param commandList do not use (required for tests)
+ */
+ @JvmStatic
+ internal fun queryDependency(
+ projectModel: ProjectModel,
+ commandList: List
+ ): Collection {
+ val chain = Chain.createForQuery(projectModel.queryType)
+
+ if (commandList.isNotEmpty()) {
+ chain.commandList.clear()
+ chain.commandList.addAll(commandList)
+ }
+
+ chain.execute(projectModel)
+
+ val lastCommand = chain.commandList.filterIsInstance()
+ .lastOrNull { it.result != null }
+ ?: return emptyList()
+
+ return lastCommand.result!!
+ }
+}
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/POMScanner.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/POMScanner.kt
new file mode 100644
index 000000000..6634f40fb
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/POMScanner.kt
@@ -0,0 +1,151 @@
+package io.github.pixee.maven.operator
+
+import org.dom4j.Element
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import java.io.File
+import java.nio.file.Path
+import java.nio.file.Paths
+import java.util.*
+import kotlin.io.path.notExists
+
+object POMScanner {
+ private val LOGGER: Logger = LoggerFactory.getLogger(POMScanner::class.java)
+
+ private val RE_WINDOWS_PATH = Regex("""^\p{Alpha}:""")
+
+ @JvmStatic
+ fun scanFrom(originalFile: File, topLevelDirectory: File): ProjectModelFactory {
+ val originalDocument = ProjectModelFactory.load(originalFile)
+
+ val parentPoms: List = try {
+ getParentPoms(originalFile)
+ } catch (e: Exception) {
+ LOGGER.warn("While trying embedder: ", e)
+
+ return legacyScanFrom(originalFile, topLevelDirectory)
+ }
+
+ return originalDocument
+ .withParentPomFiles(parentPoms.map { POMDocumentFactory.load(it) })
+ }
+
+ @JvmStatic
+ fun legacyScanFrom(originalFile: File, topLevelDirectory: File): ProjectModelFactory {
+ val pomFile: POMDocument = POMDocumentFactory.load(originalFile)
+ val parentPomFiles: MutableList = arrayListOf()
+
+ val pomFileQueue: Queue = LinkedList()
+
+ val relativePathElement =
+ pomFile.pomDocument.rootElement.element("parent")?.element("relativePath")
+
+ if (relativePathElement != null && relativePathElement.textTrim.isNotEmpty()) {
+ pomFileQueue.add(relativePathElement)
+ }
+
+ var lastFile: File = originalFile
+
+ fun resolvePath(baseFile: File, relativePath: String): Path {
+ var parentDir = baseFile
+
+ if (parentDir.isFile) {
+ parentDir = parentDir.parentFile
+ }
+
+ val result = File(File(parentDir, relativePath).toURI().normalize().path)
+
+ lastFile = if (result.isDirectory) {
+ result
+ } else {
+ result.parentFile
+ }
+
+ return Paths.get(result.absolutePath)
+ }
+
+ val prevPaths: MutableSet = linkedSetOf()
+
+ while (pomFileQueue.isNotEmpty()) {
+ val relativePathElement = pomFileQueue.poll()
+
+ if (relativePathElement.textTrim.isEmpty()) {
+ break
+ }
+
+ val relativePath = fixPomRelativePath(relativePathElement.text)
+
+ if (!isRelative(relativePath))
+ throw InvalidPathException(pomFile.file, relativePath)
+
+ if (prevPaths.contains(relativePath)) {
+ throw InvalidPathException(pomFile.file, relativePath, loop = true)
+ } else {
+ prevPaths.add(relativePath)
+ }
+
+ val newPath = resolvePath(lastFile, relativePath)
+
+ if (newPath.notExists())
+ throw InvalidPathException(pomFile.file, relativePath)
+
+ if (!newPath.startsWith(topLevelDirectory.absolutePath))
+ throw InvalidPathException(pomFile.file, relativePath)
+
+ val newPomFile = POMDocumentFactory.load(newPath.toFile())
+
+ parentPomFiles.add(newPomFile)
+
+ val newRelativePathElement =
+ newPomFile.pomDocument.rootElement.element("parent")?.element("relativePath")
+
+ if (newRelativePathElement != null) {
+ pomFileQueue.add(newRelativePathElement)
+ }
+ }
+
+ return ProjectModelFactory.loadFor(
+ pomFile = pomFile,
+ parentPomFiles = parentPomFiles
+ )
+ }
+
+ private fun fixPomRelativePath(text: String?): String {
+ if (null == text)
+ return ""
+
+ val name = File(text).name
+
+ if (-1 == name.indexOf(".")) {
+ return "$text/pom.xml"
+ }
+
+ return text
+ }
+
+ private fun isRelative(path: String): Boolean {
+ if (path.matches(RE_WINDOWS_PATH)) {
+ return false
+ }
+
+ return !(path.startsWith("/") || path.startsWith("~"))
+ }
+
+
+ private fun getParentPoms(originalFile: File): List {
+ val embedderFacadeResponse = EmbedderFacade.invokeEmbedder(
+ EmbedderFacadeRequest(offline = true, pomFile = originalFile)
+ )
+
+ val res = embedderFacadeResponse.modelBuildingResult
+
+ val rawModels = res.modelIds.map { res.getRawModel(it) }.toList()
+
+ val parentPoms: List =
+ if (rawModels.size > 1) {
+ rawModels.subList(1, rawModels.size).mapNotNull { it.pomFile }.toList()
+ } else
+ emptyList()
+ return parentPoms
+ }
+}
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/ProjectModel.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/ProjectModel.kt
new file mode 100644
index 000000000..73709090b
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/ProjectModel.kt
@@ -0,0 +1,131 @@
+package io.github.pixee.maven.operator
+
+import io.github.pixee.maven.operator.Util.selectXPathNodes
+import org.dom4j.Element
+import java.io.File
+
+/**
+ * ProjectModel represents the input parameters for the chain
+ *
+ * @todo consider resolution and also Topological Sort of Properties for cross-property reference
+ */
+class ProjectModel internal constructor(
+ val pomFile: POMDocument,
+
+ val parentPomFiles: List = emptyList(),
+
+ var dependency: Dependency?,
+ val skipIfNewer: Boolean,
+ val useProperties: Boolean,
+ val activeProfiles: Set,
+ val overrideIfAlreadyExists: Boolean,
+ val queryType: QueryType = QueryType.NONE,
+
+ val repositoryPath: File? = null,
+
+ val offline: Boolean = false,
+) {
+ internal var modifiedByCommand = false
+
+ /**
+ * Involved POM Files
+ */
+ val allPomFiles: Collection
+ get() = listOfNotNull(
+ pomFile,
+ *parentPomFiles.toTypedArray()
+ )
+
+ val resolvedProperties =
+ run {
+ val result: MutableMap = LinkedHashMap()
+
+ allPomFiles
+ .reversed() // parent first, children later - thats why its reversed
+ .forEach { pomFile ->
+ val rootProperties =
+ propertiesDefinedOnPomDocument(pomFile)
+
+ result.putAll(rootProperties)
+
+ val activatedProfiles = activeProfiles.filterNot { it.startsWith("!") }
+
+ val newPropertiesFromProfiles = activatedProfiles.map { profileName ->
+ getPropertiesFromProfile(profileName, pomFile)
+ }
+
+ newPropertiesFromProfiles.forEach { result.putAll(it) }
+ }
+
+ result.toMap()
+ }
+
+ val propertiesDefinedByFile: Map>> =
+ run {
+ val result: MutableMap>> = LinkedHashMap()
+
+ allPomFiles
+ .reversed()
+ .forEach { pomFile ->
+ val rootProperties =
+ propertiesDefinedOnPomDocument(pomFile)
+
+ val tempProperties: MutableMap = LinkedHashMap()
+
+ tempProperties.putAll(rootProperties)
+
+ val activatedProfiles = activeProfiles.filterNot { it.startsWith("!") }
+
+ val newPropertiesFromProfiles = activatedProfiles.map { profileName ->
+ getPropertiesFromProfile(profileName, pomFile)
+ }
+
+ newPropertiesFromProfiles.forEach { tempProperties.putAll(it) }
+
+ tempProperties.entries.forEach { entry ->
+ if (!result.containsKey(entry.key)) {
+ result[entry.key] = ArrayList()
+ }
+
+ val definitionList =
+ result[entry.key] as MutableList>
+
+ definitionList.add(entry.value to pomFile)
+ }
+ }
+
+ result
+ }
+
+ private fun getPropertiesFromProfile(
+ profileName: String,
+ pomFile: POMDocument
+ ): Map {
+ val expression =
+ "/m:project/m:profiles/m:profile[./m:id[text()='${profileName}']]/m:properties"
+ val propertiesElements =
+ pomFile.pomDocument.selectXPathNodes(expression)
+
+ val newPropertiesToAppend =
+ propertiesElements.filterIsInstance()
+ .flatMap { it.elements() }
+ .associate {
+ it.name to it.text
+ }
+
+ return newPropertiesToAppend
+ }
+
+ companion object {
+ fun propertiesDefinedOnPomDocument(pomFile: POMDocument): Map {
+ val rootProperties =
+ pomFile.pomDocument.rootElement.elements("properties")
+ .flatMap { it.elements() }
+ .associate {
+ it.name to it.text
+ }
+ return rootProperties
+ }
+ }
+}
+
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/ProjectModelFactory.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/ProjectModelFactory.kt
new file mode 100644
index 000000000..80191385e
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/ProjectModelFactory.kt
@@ -0,0 +1,151 @@
+package io.github.pixee.maven.operator
+
+import java.io.File
+import java.io.InputStream
+import java.net.URL
+
+/**
+ * Builder Object for ProjectModel instances
+ */
+class ProjectModelFactory private constructor(
+ private var pomFile: POMDocument,
+ private var parentPomFiles: List = listOf(),
+ private var dependency: Dependency? = null,
+ private var skipIfNewer: Boolean = false,
+ private var useProperties: Boolean = false,
+ private var activeProfiles: Set = emptySet(),
+ private var overrideIfAlreadyExists: Boolean = false,
+ private var queryType: QueryType = QueryType.NONE,
+ private var repositoryPath: File? = null,
+ private var offline: Boolean = false,
+) {
+ /**
+ * Fluent Setter
+ *
+ * @param pomFile POM File
+ */
+ fun withPomFile(pomFile: POMDocument): ProjectModelFactory = this.apply {
+ this.pomFile = pomFile
+ }
+
+ /**
+ * Fluent Setter
+ *
+ * @param parentPomFiles Parent POM Files
+ */
+ fun withParentPomFiles(parentPomFiles: Collection): ProjectModelFactory =
+ this.apply {
+ this.parentPomFiles = listOf(*parentPomFiles.filterNotNull().toTypedArray())
+ }
+
+ /**
+ * Fluent Setter
+ *
+ * @param dep dependency
+ */
+ fun withDependency(dep: Dependency): ProjectModelFactory = this.apply {
+ this.dependency = dep
+ }
+
+ /**
+ * Fluent Setter
+ */
+ fun withSkipIfNewer(skipIfNewer: Boolean): ProjectModelFactory = this.apply {
+ this.skipIfNewer = skipIfNewer
+ }
+
+ /**
+ * Fluent Setter
+ */
+ fun withUseProperties(useProperties: Boolean): ProjectModelFactory = this.apply {
+ this.useProperties = useProperties
+ }
+
+ /**
+ * Fluent Setter
+ */
+ fun withActiveProfiles(vararg activeProfiles: String): ProjectModelFactory = this.apply {
+ this.activeProfiles = setOf(*activeProfiles)
+ }
+
+ /**
+ * Fluent Setter
+ */
+ fun withOverrideIfAlreadyExists(overrideIfAlreadyExists: Boolean) = this.apply {
+ this.overrideIfAlreadyExists = overrideIfAlreadyExists
+ }
+
+ /**
+ * Fluent Setter
+ *
+ * @param queryType query type
+ */
+ fun withQueryType(queryType: QueryType) = this.apply {
+ this.queryType = queryType
+ }
+
+ /**
+ * Fluent Setter
+ *
+ * @param repositoryPath Repository Path
+ */
+ fun withRepositoryPath(repositoryPath: File?) = this.apply {
+ this.repositoryPath = repositoryPath
+ }
+
+ /**
+ * Fluent Setter
+ *
+ * @param offline Offline
+ */
+ fun withOffline(offline: Boolean) = this.apply {
+ this.offline = offline
+ }
+
+ /**
+ * Fluent Setter
+ */
+ fun build(): ProjectModel {
+ return ProjectModel(
+ pomFile = pomFile,
+ parentPomFiles = parentPomFiles,
+ dependency = dependency,
+ skipIfNewer = skipIfNewer,
+ useProperties = useProperties,
+ activeProfiles = activeProfiles,
+ overrideIfAlreadyExists = overrideIfAlreadyExists,
+ queryType = queryType,
+ repositoryPath = repositoryPath,
+ offline = offline,
+ )
+ }
+
+ /**
+ * Mostly Delegates to POMDocumentFactory
+ */
+ companion object {
+ @JvmStatic
+ fun load(`is`: InputStream): ProjectModelFactory {
+ val pomDocument = POMDocumentFactory.load(`is`)
+
+ return ProjectModelFactory(pomFile = pomDocument)
+ }
+
+ @JvmStatic
+ fun load(f: File) =
+ load(f.toURI().toURL())
+
+ @JvmStatic
+ fun load(url: URL): ProjectModelFactory {
+ val pomFile = POMDocumentFactory.load(url)
+
+ return ProjectModelFactory(pomFile = pomFile)
+ }
+
+ @JvmStatic
+ internal fun loadFor(
+ pomFile: POMDocument,
+ parentPomFiles: Collection,
+ ) = ProjectModelFactory(pomFile = pomFile, parentPomFiles = parentPomFiles.toList())
+ }
+}
\ No newline at end of file
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/QueryByEmbedder.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/QueryByEmbedder.kt
new file mode 100644
index 000000000..973f06852
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/QueryByEmbedder.kt
@@ -0,0 +1,80 @@
+package io.github.pixee.maven.operator
+
+import org.apache.commons.io.output.NullOutputStream
+import org.apache.maven.cli.MavenCli
+import org.apache.maven.shared.invoker.InvocationRequest
+import org.apache.maven.shared.invoker.MavenCommandLineBuilder
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import java.io.ByteArrayOutputStream
+import java.io.File
+import java.io.PrintStream
+
+/**
+ * Uses Maven Embedder to Implement
+ */
+class QueryByEmbedder : AbstractQueryCommand() {
+ /**
+ * Runs the "dependency:tree" mojo - but using Embedder instead.
+ */
+ override fun extractDependencyTree(outputPath: File, pomFilePath: File, c: ProjectModel) {
+ val mavenCli = MavenCli()
+
+ val cliBuilder = MavenCommandLineBuilder()
+
+ val invocationRequest: InvocationRequest =
+ buildInvocationRequest(outputPath, pomFilePath, c)
+
+ val oldMultimoduleValue = System.getProperty(MAVEN_MULTIMODULE_PROJECT_DIRECTORY)
+
+ System.setProperty(MAVEN_MULTIMODULE_PROJECT_DIRECTORY, pomFilePath.parent)
+
+ try {
+ val cliBuilderResult = cliBuilder.build(invocationRequest)
+
+ val cliArgs = cliBuilderResult.commandline.toList().drop(1).toTypedArray()
+
+ val baosOut =
+ if (LOGGER.isDebugEnabled) ByteArrayOutputStream() else NullOutputStream.NULL_OUTPUT_STREAM
+
+ val baosErr =
+ if (LOGGER.isDebugEnabled) ByteArrayOutputStream() else NullOutputStream.NULL_OUTPUT_STREAM
+
+ val result: Int = mavenCli.doMain(
+ cliArgs,
+ pomFilePath.parent,
+ PrintStream(baosOut, true),
+ PrintStream(baosErr, true)
+ )
+
+ if (LOGGER.isDebugEnabled) {
+ LOGGER.debug("baosOut: {}", baosOut.toString())
+ LOGGER.debug("baosErr: {}", baosErr.toString())
+ }
+
+ /**
+ * Sometimes the Embedder will fail - it will return this specific exit code (1) as well as
+ * not generate this file
+ *
+ * If that happens, we'll move to the next strategy (Invoker-based likely) by throwing a
+ * custom exception which is caught inside the Chain#execute method
+ *
+ * @see Chain#execute
+ */
+ if (1 == result && (!outputPath.exists()))
+ throw InvalidContextException()
+
+ if (0 != result)
+ throw IllegalStateException("Unexpected status code: %02d".format(result))
+ } finally {
+ if (null != oldMultimoduleValue)
+ System.setProperty(MAVEN_MULTIMODULE_PROJECT_DIRECTORY, oldMultimoduleValue)
+ }
+ }
+
+ companion object {
+ const val MAVEN_MULTIMODULE_PROJECT_DIRECTORY = "maven.multiModuleProjectDirectory"
+
+ private val LOGGER: Logger = LoggerFactory.getLogger(QueryByEmbedder::class.java)
+ }
+}
\ No newline at end of file
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/QueryByInvoker.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/QueryByInvoker.kt
new file mode 100644
index 000000000..a3fb973d2
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/QueryByInvoker.kt
@@ -0,0 +1,32 @@
+package io.github.pixee.maven.operator
+
+import org.apache.maven.shared.invoker.DefaultInvoker
+import org.apache.maven.shared.invoker.InvocationRequest
+import java.io.File
+
+class QueryByInvoker : AbstractQueryCommand() {
+
+ override fun extractDependencyTree(
+ outputPath: File,
+ pomFilePath: File,
+ c: ProjectModel
+ ) {
+ val invoker = DefaultInvoker()
+
+ val invocationRequest: InvocationRequest =
+ buildInvocationRequest(outputPath, pomFilePath, c)
+
+ val invocationResult = invoker.execute(invocationRequest)
+
+ val exitCode = invocationResult.exitCode
+
+ if (0 != exitCode) {
+ throw IllegalStateException(
+ "Unexpected Status Code from Invoker: %02d".format(
+ exitCode
+ )
+ )
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/QueryByParsing.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/QueryByParsing.kt
new file mode 100644
index 000000000..617ba48d9
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/QueryByParsing.kt
@@ -0,0 +1,165 @@
+@file:Suppress("DEPRECATION")
+
+package io.github.pixee.maven.operator
+
+import org.apache.commons.lang3.builder.CompareToBuilder
+import org.apache.commons.lang3.text.StrSubstitutor
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import java.io.File
+import java.util.*
+
+/**
+ * this one is a bit more complex, as it intents to to a "best effort" attempt at parsing a pom
+ * focusing only on dependency right now, * without relying to any maven infrastructure at all
+ */
+class QueryByParsing : AbstractQueryCommand() {
+ override fun extractDependencyTree(outputPath: File, pomFilePath: File, c: ProjectModel) {
+ TODO("Not yet implemented")
+ }
+
+ private val dependencies: MutableSet = LinkedHashSet()
+
+ private val dependencyManagement: MutableSet =
+ TreeSet(object : Comparator {
+ override fun compare(o1: Dependency?, o2: Dependency?): Int {
+ if (o1 == o2)
+ return 0
+
+ if (o1 == null)
+ return 1
+
+ if (o2 == null)
+ return -1
+
+ return CompareToBuilder().append(o1.groupId, o2.groupId)
+ .append(o1.artifactId, o2.artifactId).toComparison()
+ }
+ })
+
+ private val properties: MutableMap = LinkedHashMap()
+
+ private val strSubstitutor = StrSubstitutor(properties)
+
+ override fun execute(pm: ProjectModel): Boolean {
+ /**
+ * Enlist all pom files given an hierarchy
+ */
+ val pomFilesByHierarchy = pm.allPomFiles.reversed()
+
+ for (pomDocument in pomFilesByHierarchy) {
+ updateProperties(pomDocument)
+
+ updateDependencyManagement(pomDocument)
+
+ updateDependencies(pomDocument)
+ }
+
+ this.result = dependencies
+
+ return true
+ }
+
+ private fun updateDependencyManagement(pomDocument: POMDocument) {
+ val dependencyManagementDependenciesToAdd: Collection =
+ pomDocument.pomDocument.//
+ rootElement. //
+ element("dependencyManagement")?. //
+ element("dependencies")?. //
+ elements("dependency")?. //
+ map { dependencyElement ->
+ val groupId = dependencyElement.element("groupId").text
+ val artifactId = dependencyElement.element("artifactId").text
+
+ val version = dependencyElement.element("version")?.text ?: "UNKNOWN"
+
+ val classifier = dependencyElement.element("classifier")?.text
+ val packaging = dependencyElement.element("packaging")?.text
+
+ val versionInterpolated = try {
+ strSubstitutor.replace(version)
+ } catch (e: java.lang.IllegalStateException) {
+ LOGGER.warn("while interpolating version", e)
+
+ "UNKNOWN"
+ }
+
+ Dependency(groupId, artifactId, versionInterpolated, classifier, packaging)
+ }?.toList() ?: emptyList()
+
+ this.dependencyManagement.addAll(dependencyManagementDependenciesToAdd)
+ }
+
+ fun lookForDependencyManagement(groupId: String, artifactId: String): Dependency? =
+ this.dependencyManagement.firstOrNull { it.groupId == groupId && it.artifactId == artifactId }
+
+ private fun updateDependencies(pomDocument: POMDocument) {
+ val dependenciesToAdd: Collection =
+ pomDocument.pomDocument.//
+ rootElement. //
+ element("dependencies")?. //
+ elements("dependency")?. //
+ map { dependencyElement ->
+ val groupId = dependencyElement.element("groupId").text
+ val artifactId = dependencyElement.element("artifactId").text
+
+ val versionElement = dependencyElement.element("version")
+
+ val proposedDependency = lookForDependencyManagement(groupId, artifactId)
+
+ if (versionElement == null && null != proposedDependency) {
+ proposedDependency
+ } else {
+ val version = versionElement?.text ?: "UNKNOWN"
+
+ val classifier = dependencyElement.element("classifier")?.text
+ val packaging = dependencyElement.element("packaging")?.text
+
+ val versionInterpolated = try {
+ strSubstitutor.replace(version)
+ } catch (e: java.lang.IllegalStateException) {
+ LOGGER.warn("while interpolating version", e)
+
+ "UNKNOWN"
+ }
+
+ Dependency(groupId, artifactId, versionInterpolated, classifier, packaging)
+ }
+ }?.toList() ?: emptyList()
+
+ this.dependencies.addAll(
+ dependenciesToAdd
+ )
+ }
+
+ /**
+ * Updates the Properties member variable based on whats on the POMDocument
+ */
+ private fun updateProperties(pomDocument: POMDocument) {
+ val propsDefined = ProjectModel.propertiesDefinedOnPomDocument(pomDocument)
+
+ propsDefined.entries.filterNot { it.value.matches(RE_INTERPOLATION) }
+ .forEach {
+ properties[it.key] = it.value
+ }
+
+ propsDefined.entries.filterNot { it.value.matches(RE_INTERPOLATION) }
+ .forEach {
+ val newValue = try {
+ strSubstitutor.replace(it.value)
+ } catch (e: IllegalStateException) {
+ LOGGER.warn("while replacing variables: ", e)
+
+ it.value
+ }
+
+ properties.put(it.key, it.value)
+ }
+ }
+
+ companion object {
+ val RE_INTERPOLATION = Regex(""".*\$\{[\p{Alnum}.-_]+\}.*""")
+
+ val logger: Logger = LoggerFactory.getLogger(QueryByParsing::class.java)
+ }
+}
\ No newline at end of file
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/QueryByResolver.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/QueryByResolver.kt
new file mode 100644
index 000000000..a54094f59
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/QueryByResolver.kt
@@ -0,0 +1,115 @@
+package io.github.pixee.maven.operator
+
+import org.apache.maven.model.building.ModelBuildingException
+import org.eclipse.aether.artifact.DefaultArtifact
+import org.eclipse.aether.collection.CollectRequest
+import org.eclipse.aether.collection.DependencyCollectionException
+import org.eclipse.aether.graph.DependencyNode
+import org.eclipse.aether.graph.DependencyVisitor
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import java.io.File
+
+/**
+ * This is a resolver that actually embeds much of Maven Logic into that.
+ *
+ * Futurely TODO Support Third Party / User-Supplied Repositories (right now it only supports central)
+ */
+class QueryByResolver : AbstractQueryCommand() {
+ companion object {
+ private val LOGGER: Logger = LoggerFactory.getLogger(QueryByResolver::class.java)
+ }
+
+ override fun extractDependencyTree(outputPath: File, pomFilePath: File, c: ProjectModel) {
+ TODO("Not yet implemented")
+ }
+
+ override fun execute(pm: ProjectModel): Boolean {
+ val req = EmbedderFacadeRequest(
+ offline = pm.offline,
+ pomFile = pm.pomFile.file,
+ localRepositoryPath = pm.repositoryPath,
+ activeProfileIds = pm.activeProfiles.filterNot { it.startsWith("!") }.toList(),
+ inactiveProfileIds = pm.activeProfiles.filter { it.startsWith("!") }
+ .map { it.substring(1) }.toList()
+ )
+
+ this.result = emptyList()
+
+ val embedderFacadeResponse: EmbedderFacadeResponse
+
+ try {
+ embedderFacadeResponse = EmbedderFacade.invokeEmbedder(req)
+ } catch (mbe: ModelBuildingException) {
+ LOGGER.warn("Oops:", mbe)
+
+ return false
+ }
+
+ val res = embedderFacadeResponse.modelBuildingResult
+
+ val dependencyToArtifact: (org.apache.maven.model.Dependency) -> org.eclipse.aether.graph.Dependency =
+ {
+ org.eclipse.aether.graph.Dependency(
+ DefaultArtifact(
+ it.groupId,
+ it.artifactId,
+ it.classifier,
+ null,
+ it.version
+ ),
+ it.scope,
+ )
+ }
+
+ val deps: List =
+ res.effectiveModel.dependencies?.map(dependencyToArtifact)?.toList() ?: emptyList()
+
+ val managedDeps: List =
+ res.effectiveModel.dependencyManagement?.dependencies?.map(dependencyToArtifact)
+ ?.toList() ?: emptyList()
+
+ val collectRequest =
+ CollectRequest(deps, managedDeps, embedderFacadeResponse.remoteRepositories)
+
+ return try {
+ val collectResult = embedderFacadeResponse.repositorySystem!!.collectDependencies(
+ embedderFacadeResponse.session,
+ collectRequest
+ )
+
+ val returnList: MutableList = mutableListOf()
+
+ collectResult.root.accept(object : DependencyVisitor {
+ override fun visitEnter(node: DependencyNode?): Boolean {
+ node?.dependency?.apply {
+ returnList.add(
+ Dependency(
+ groupId = this.artifact.groupId,
+ artifactId = this.artifact.artifactId,
+ version = this.artifact.version,
+ classifier = this.artifact.classifier,
+ packaging = this.artifact.extension,
+ scope = this.scope,
+ )
+ )
+ }
+
+ return true
+ }
+
+ override fun visitLeave(node: DependencyNode?): Boolean {
+ return true
+ }
+ })
+
+ this.result = returnList.toList()
+
+ return true
+ } catch (e: DependencyCollectionException) {
+ LOGGER.warn("while resolving: ", e)
+
+ return false
+ }
+ }
+}
\ No newline at end of file
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/QueryType.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/QueryType.kt
new file mode 100644
index 000000000..14d22cbd6
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/QueryType.kt
@@ -0,0 +1,7 @@
+package io.github.pixee.maven.operator
+
+enum class QueryType {
+ NONE,
+ SAFE,
+ UNSAFE,
+}
\ No newline at end of file
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/SimpleDependencyManagement.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/SimpleDependencyManagement.kt
new file mode 100644
index 000000000..48b6366fa
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/SimpleDependencyManagement.kt
@@ -0,0 +1,13 @@
+package io.github.pixee.maven.operator
+
+import io.github.pixee.maven.operator.Util.buildLookupExpressionForDependencyManagement
+
+
+val SimpleDependencyManagement = object : AbstractCommand() {
+ override fun execute(pm: ProjectModel): Boolean {
+ val lookupExpression =
+ buildLookupExpressionForDependencyManagement(pm.dependency!!)
+
+ return handleDependency(pm, lookupExpression)
+ }
+}
\ No newline at end of file
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/SimpleInsert.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/SimpleInsert.kt
new file mode 100644
index 000000000..03c11d31f
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/SimpleInsert.kt
@@ -0,0 +1,77 @@
+package io.github.pixee.maven.operator
+
+import io.github.pixee.maven.operator.Util.addIndentedElement
+import io.github.pixee.maven.operator.Util.selectXPathNodes
+import io.github.pixee.maven.operator.Util.upgradeVersionNode
+import org.dom4j.Element
+
+/**
+ * Represents a POM Upgrade Strategy by simply adding a dependency/ section (and optionally a dependencyManagement/ section as well)
+ */
+val SimpleInsert = object : Command {
+ override fun execute(pm: ProjectModel): Boolean {
+ val dependencyManagementNodeList =
+ pm.pomFile.resultPom.selectXPathNodes("/m:project/m:dependencyManagement")
+
+ val dependenciesNode = if (dependencyManagementNodeList.isEmpty()) {
+ val newDependencyManagementNode =
+ pm.pomFile.resultPom.rootElement.addIndentedElement(
+ pm.pomFile,
+ "dependencyManagement"
+ )
+
+ val dependencyManagementNode =
+ newDependencyManagementNode.addIndentedElement(pm.pomFile, "dependencies")
+
+ dependencyManagementNode
+ } else {
+ (dependencyManagementNodeList.first() as Element).element("dependencies")
+ }
+
+ val dependencyNode = appendCoordinates(dependenciesNode, pm)
+
+ val versionNode = dependencyNode.addIndentedElement(pm.pomFile, "version")
+
+ upgradeVersionNode(pm, versionNode, pm.pomFile)
+
+ val dependenciesNodeList =
+ pm.pomFile.resultPom.selectXPathNodes("//m:project/m:dependencies")
+
+ val rootDependencyNode: Element = if (dependenciesNodeList.isEmpty()) {
+ pm.pomFile.resultPom.rootElement.addIndentedElement(pm.pomFile, "dependencies")
+ } else if (dependenciesNodeList.size == 1) {
+ dependenciesNodeList[0] as Element
+ } else {
+ throw IllegalStateException("More than one dependencies node")
+ }
+
+ appendCoordinates(rootDependencyNode, pm)
+
+ return true
+ }
+
+ /**
+ * Creates the XML Elements for a given dependency
+ */
+ private fun appendCoordinates(
+ dependenciesNode: Element,
+ c: ProjectModel
+ ): Element {
+ val dependencyNode = dependenciesNode.addIndentedElement(c.pomFile, "dependency")
+
+ val groupIdNode = dependencyNode.addIndentedElement(c.pomFile, "groupId")
+
+ val dep = c.dependency!!
+
+ groupIdNode.text = dep.groupId
+
+ val artifactIdNode = dependencyNode.addIndentedElement(c.pomFile, "artifactId")
+
+ artifactIdNode.text = dep.artifactId
+
+ return dependencyNode
+ }
+
+ override fun postProcess(c: ProjectModel): Boolean = false
+}
+
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/SimpleUpgrade.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/SimpleUpgrade.kt
new file mode 100644
index 000000000..7e268befa
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/SimpleUpgrade.kt
@@ -0,0 +1,16 @@
+package io.github.pixee.maven.operator
+
+import io.github.pixee.maven.operator.Util.buildLookupExpressionForDependency
+
+
+/**
+ * Represents bumping an existing dependency/
+ */
+val SimpleUpgrade = object : AbstractCommand() {
+ override fun execute(pm: ProjectModel): Boolean {
+ val lookupExpressionForDependency =
+ buildLookupExpressionForDependency(pm.dependency!!)
+
+ return handleDependency(pm, lookupExpressionForDependency)
+ }
+}
\ No newline at end of file
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/SupportCommand.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/SupportCommand.kt
new file mode 100644
index 000000000..be9286d58
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/SupportCommand.kt
@@ -0,0 +1,6 @@
+package io.github.pixee.maven.operator
+
+/**
+ * Tag Interface to the chain to allow it to figure out whether things were modified
+ */
+interface SupportCommand
\ No newline at end of file
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/Util.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/Util.kt
new file mode 100644
index 000000000..3094a2973
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/Util.kt
@@ -0,0 +1,283 @@
+@file:Suppress("DEPRECATION")
+
+package io.github.pixee.maven.operator
+
+import com.github.zafarkhaja.semver.Version
+import org.apache.commons.lang3.StringUtils
+import org.apache.commons.lang3.SystemUtils
+import org.apache.commons.lang3.text.StrSubstitutor
+import org.dom4j.Element
+import org.dom4j.Node
+import org.dom4j.Text
+import org.dom4j.tree.DefaultText
+import org.jaxen.SimpleNamespaceContext
+import org.jaxen.XPath
+import org.jaxen.dom4j.Dom4jXPath
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import java.io.File
+
+
+/**
+ * Common Utilities
+ */
+object Util {
+ private val LOGGER: Logger = LoggerFactory.getLogger(Util::class.java)
+
+ /**
+ * Extension Method that easily allows to add an element inside another while
+ * retaining formatting
+ *
+ * @param d ProjectModel / Context
+ * @param name new element ("tag") name
+ * @return created element inside `this` object, already indented after and (optionally) before
+ */
+ fun Element.addIndentedElement(d: POMDocument, name: String): Element {
+ val contentList = this.content()
+
+ val indentLevel = findIndentLevel(this)
+
+ val prefix = d.endl + StringUtils.repeat(d.indent, 1 + indentLevel)
+
+ val suffix = d.endl + StringUtils.repeat(d.indent, indentLevel)
+
+ if (contentList.isNotEmpty() && contentList.last() is Text) {
+ val lastElement = contentList.last() as Text
+
+ if (StringUtils.isWhitespace(lastElement.text)) {
+ contentList.remove(contentList.last())
+ }
+ }
+
+ contentList.add(DefaultText(prefix))
+
+ val newElement = this.addElement(name)
+
+ contentList.add(DefaultText(suffix))
+
+ d.dirty = true
+
+ return newElement
+ }
+
+ /**
+ * Guesses the current indent level of the nearest nodes
+ *
+ * @return indent level
+ */
+ private fun findIndentLevel(startingNode: Element): Int {
+ var level = 0
+
+ var node = startingNode
+
+ while (node.parent != null) {
+ level += 1
+ node = node.parent
+ }
+
+ return level
+ }
+
+ /**
+ * Represents a Property Reference - as a regex
+ */
+ private val PROPERTY_REFERENCE_REGEX = Regex("^\\\$\\{(.*)}$")
+
+ /**
+ * Upserts a given property
+ */
+ private fun upgradeProperty(c: ProjectModel, d: POMDocument, propertyName: String) {
+ if (null == d.resultPom.rootElement.element("properties")) {
+ d.resultPom.rootElement.addIndentedElement(d, "properties")
+ }
+
+ val parentPropertyElement = d.resultPom.rootElement.element("properties")
+
+ if (null == parentPropertyElement.element(propertyName)) {
+ parentPropertyElement.addIndentedElement(d, propertyName)
+ } else {
+ if (!c.overrideIfAlreadyExists) {
+ val propertyReferenceRE = Regex.fromLiteral("\${$propertyName}")
+
+ val numberOfAllCurrentMatches =
+ propertyReferenceRE.findAll(d.resultPom.asXML()).toList().size
+
+ if (numberOfAllCurrentMatches > 1) {
+ throw IllegalStateException("Property $propertyName is already defined - and used more than once.")
+ }
+ }
+ }
+
+ val propertyElement = parentPropertyElement.element(propertyName)
+
+ if (!(propertyElement.text ?: "").trim().equals(c.dependency!!.version)) {
+ propertyElement.text = c.dependency!!.version
+
+ d.dirty = true
+ }
+ }
+
+ /**
+ * Creates a property Name
+ */
+ internal fun propertyName(c: ProjectModel, versionNode: Element): String {
+ val version = versionNode.textTrim
+
+ if (PROPERTY_REFERENCE_REGEX.matches(version)) {
+ val match = PROPERTY_REFERENCE_REGEX.find(version)
+
+ val firstMatch = match!!.groups[1]!!
+
+ return firstMatch.value
+ }
+
+ return "versions." + c.dependency!!.artifactId
+ }
+
+ /**
+ * Identifies if an upgrade is needed
+ */
+ internal fun findOutIfUpgradeIsNeeded(c: ProjectModel, versionNode: Element): Boolean {
+ val currentVersionNodeText = resolveVersion(c, versionNode.text!!)
+
+ val currentVersion = Version.valueOf(currentVersionNodeText)
+ val newVersion = Version.valueOf(c.dependency!!.version)
+
+ @Suppress("UnnecessaryVariable") val versionsAreIncreasing =
+ newVersion.greaterThan(currentVersion)
+
+ return versionsAreIncreasing
+ }
+
+ private fun resolveVersion(c: ProjectModel, versionText: String): String =
+ if (PROPERTY_REFERENCE_REGEX.matches(versionText)) {
+ @Suppress("DEPRECATION")
+ StrSubstitutor(c.resolvedProperties).replace(versionText)
+ } else {
+ versionText
+ }
+
+ /**
+ * Escapes a Property Name
+ */
+ private fun escapedPropertyName(propertyName: String): String =
+ "\${$propertyName}"
+
+ /**
+ * Given a Version Node, upgrades a resulting POM
+ */
+ internal fun upgradeVersionNode(
+ c: ProjectModel,
+ versionNode: Element,
+ pomDocumentHoldingProperty: POMDocument
+ ) {
+ if (c.useProperties) {
+ val propertyName = propertyName(c, versionNode)
+
+ // define property
+ upgradeProperty(c, pomDocumentHoldingProperty, propertyName)
+
+ versionNode.text = escapedPropertyName(propertyName)
+ } else {
+ if (!(versionNode.text ?: "").trim().equals(c.dependency!!.version)) {
+ pomDocumentHoldingProperty.dirty = true
+ versionNode.text = c.dependency!!.version
+ }
+ }
+ }
+
+ /**
+ * Builds a Lookup Expression String for a given dependency
+ *
+ * @param dependency Dependency
+ */
+ fun buildLookupExpressionForDependency(dependency: Dependency): String =
+ "/m:project" +
+ "/m:dependencies" +
+ "/m:dependency" +
+ /* */ "[./m:groupId[text()='${dependency.groupId}'] and " +
+ /* */ "./m:artifactId[text()='${dependency.artifactId}']" +
+ "]"
+
+ /**
+ * Builds a Lookup Expression String for a given dependency, but under the >dependencyManagement> section
+ *
+ * @param dependency Dependency
+ */
+ fun buildLookupExpressionForDependencyManagement(dependency: Dependency): String =
+ "/m:project" +
+ "/m:dependencyManagement" +
+ "/m:dependencies" +
+ "/m:dependency" +
+ /* */ "[./m:groupId[text()='${dependency.groupId}'] and " +
+ /* */ "./m:artifactId[text()='${dependency.artifactId}']" +
+ "]"
+
+ /**
+ * Extension Function to Select the XPath Nodes
+ *
+ * @param expression expression to use
+ */
+ @Suppress("UNCHECKED_CAST")
+ fun Node.selectXPathNodes(expression: String) =
+ createXPathExpression(expression).selectNodes(this)!! as List
+
+ /**
+ * Creates a XPath Expression from a given expression string
+ *
+ * @param expression expression to create xpath from
+ */
+ private fun createXPathExpression(expression: String): XPath {
+ val xpath = Dom4jXPath(expression)
+
+ xpath.namespaceContext = namespaceContext
+
+ return xpath
+ }
+
+ /**
+ * Hard-Coded POM Namespace Map
+ */
+ private val namespaceContext = SimpleNamespaceContext(
+ mapOf(
+ "m" to "http://maven.apache.org/POM/4.0.0"
+ )
+ )
+
+
+ internal fun which(path: String): File? {
+ val nativeExecutables: List = if (SystemUtils.IS_OS_WINDOWS) {
+ listOf("", ".exe", ".bat", ".cmd").map { path + it }.toList()
+ } else {
+ listOf(path)
+ }
+
+ val pathContentString = System.getenv("PATH")
+
+ val pathElements = pathContentString.split(File.pathSeparatorChar)
+
+ val possiblePaths = nativeExecutables.flatMap { executable ->
+ pathElements.map { pathElement ->
+ File(File(pathElement), executable)
+ }
+ }
+
+ val isCliCallable: (File) -> Boolean = if (SystemUtils.IS_OS_WINDOWS) { it ->
+ it.exists() && it.isFile
+ } else { it ->
+ it.exists() && it.isFile && it.canExecute()
+ }
+
+ val result = possiblePaths.findLast(isCliCallable)
+
+ if (null == result) {
+ LOGGER.warn(
+ "Unable to find mvn executable (execs: {}, path: {})",
+ nativeExecutables.joinToString("/"),
+ pathContentString
+ )
+ }
+
+ return result
+ }
+}
diff --git a/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/WrongDependencyTypeException.kt b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/WrongDependencyTypeException.kt
new file mode 100644
index 000000000..9c13bbe89
--- /dev/null
+++ b/pom-operator/src/main/kotlin/io/github/pixee/maven/operator/WrongDependencyTypeException.kt
@@ -0,0 +1,3 @@
+package io.github.pixee.maven.operator
+
+class WrongDependencyTypeException(message: String) : RuntimeException(message)
diff --git a/pom-operator/src/test/java/io/github/pixee/maven/operator/test/POMOperatorJavaIT.java b/pom-operator/src/test/java/io/github/pixee/maven/operator/test/POMOperatorJavaIT.java
new file mode 100644
index 000000000..582f21ee4
--- /dev/null
+++ b/pom-operator/src/test/java/io/github/pixee/maven/operator/test/POMOperatorJavaIT.java
@@ -0,0 +1,40 @@
+package io.github.pixee.maven.operator.test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import io.github.pixee.maven.operator.Util;
+import java.util.Arrays;
+import java.util.List;
+import org.apache.commons.lang3.SystemUtils;
+import org.junit.jupiter.api.Test;
+
+public class POMOperatorJavaIT {
+ @Test
+ public void testJavaSample() throws Exception {
+ String mvnAbsPath = Util.INSTANCE.which$pom_operator("mvn").getAbsolutePath();
+
+ List argList =
+ Arrays.asList(mvnAbsPath, "-B", "-N", "-f", "java-sample/pom.xml", "verify");
+
+ if (SystemUtils.IS_OS_WINDOWS) {
+ List newArgList =
+ Arrays.asList(Util.INSTANCE.which$pom_operator("cmd").getAbsolutePath(), "/c");
+
+ newArgList.addAll(argList);
+
+ argList = newArgList;
+ }
+
+ String[] args = argList.toArray(new String[0]);
+
+ ProcessBuilder psBuilder = new ProcessBuilder(args).inheritIO();
+
+ psBuilder.environment().putAll(System.getenv());
+
+ Process process = psBuilder.start();
+
+ int retCode = process.waitFor();
+
+ assertEquals(0, retCode, "Embedded execution must return zero");
+ }
+}
diff --git a/pom-operator/src/test/kotlin/io/github/pixee/maven/operator/test/AbstractTestBase.kt b/pom-operator/src/test/kotlin/io/github/pixee/maven/operator/test/AbstractTestBase.kt
new file mode 100644
index 000000000..9424d3e6b
--- /dev/null
+++ b/pom-operator/src/test/kotlin/io/github/pixee/maven/operator/test/AbstractTestBase.kt
@@ -0,0 +1,110 @@
+package io.github.pixee.maven.operator.test
+
+import `fun`.mike.dmp.DiffMatchPatch
+import io.github.pixee.maven.operator.POMOperator
+import io.github.pixee.maven.operator.ProjectModel
+import io.github.pixee.maven.operator.ProjectModelFactory
+import org.dom4j.Document
+import org.dom4j.io.SAXReader
+import org.junit.jupiter.api.Assertions.assertFalse
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import org.xmlunit.builder.DiffBuilder
+import org.xmlunit.builder.Input
+import org.xmlunit.diff.Diff
+import java.io.File
+import java.net.URLDecoder
+
+open class AbstractTestBase {
+ protected val LOGGER: Logger = LoggerFactory.getLogger(POMOperatorTest::class.java)
+
+ fun getResource(name: String) = this.javaClass.getResource(name)!!
+
+ fun getResourceAsFile(name: String): File = File(getResource(name).toURI())
+
+ /**
+ * Implements a Given-When-Then idiom
+ *
+ * @param g: Given - returns a context
+ * @param t: Then - validates given a context/ProjectModel
+ */
+ protected fun gwt(g: () -> ProjectModel, t: (p: ProjectModel) -> Unit) {
+ val context = g()
+
+ LOGGER.debug("context: {}", context)
+
+ POMOperator.modify(context)
+
+ LOGGER.debug("context after: {}", context)
+
+ t(context)
+ }
+
+ protected fun gwt(name: String, pmf: ProjectModelFactory): ProjectModel =
+ gwt(name, pmf.build())
+
+ protected fun gwt(testName: String, context: ProjectModel): ProjectModel {
+ val resultFile = "pom-$testName-result.xml"
+ val resource = this.javaClass.javaClass.getResource(resultFile)
+
+ if (resource != null) {
+ val outcome = SAXReader().read(resource)
+
+ LOGGER.debug("context: {}", context)
+
+ POMOperator.modify(context)
+
+ LOGGER.debug("context after: {}", context)
+
+ assertFalse(
+ getXmlDifferences(context.pomFile.resultPom, outcome).hasDifferences(),
+ "Expected and outcome have differences"
+ )
+ } else {
+ val resultFilePath = "src/test/resources/" + this.javaClass.`package`.name.replace(
+ ".",
+ "/"
+ ) + "/" + resultFile
+
+ LOGGER.debug("context: {}", context)
+
+ POMOperator.modify(context)
+
+ LOGGER.debug("context after: {}", context)
+
+ LOGGER.warn("File $resultFilePath not found - writing results instead and ignorning assertions at all")
+
+ File(resultFilePath).writeBytes(context.pomFile.resultPomBytes)
+ }
+
+ return context
+ }
+
+ protected fun getXmlDifferences(
+ original: Document,
+ modified: Document
+ ): Diff {
+ val originalDoc = Input.fromString(original.asXML()).build()
+ val modifiedDoc = Input.fromString(modified.asXML()).build()
+
+ val diff = DiffBuilder.compare(originalDoc).withTest(modifiedDoc).ignoreWhitespace()
+ .checkForSimilar().build()
+
+ LOGGER.debug("diff: {}", diff)
+
+ return diff
+ }
+
+ protected fun getTextDifferences(pomDocument: Document, resultPom: Document): Any {
+ val pomDocumentAsString = pomDocument.asXML()
+ val resultPomAsString = resultPom.asXML()
+
+ val dmp = DiffMatchPatch()
+
+ val diffs = dmp.patch_make(pomDocumentAsString, resultPomAsString)
+
+ val patch = dmp.patch_toText(diffs)
+
+ return URLDecoder.decode(patch, "utf-8")
+ }
+}
diff --git a/pom-operator/src/test/kotlin/io/github/pixee/maven/operator/test/MassRepoTest.kt b/pom-operator/src/test/kotlin/io/github/pixee/maven/operator/test/MassRepoTest.kt
new file mode 100644
index 000000000..a0d42ccac
--- /dev/null
+++ b/pom-operator/src/test/kotlin/io/github/pixee/maven/operator/test/MassRepoTest.kt
@@ -0,0 +1,321 @@
+package io.github.pixee.maven.operator.test
+
+import io.github.pixee.maven.operator.Dependency
+import io.github.pixee.maven.operator.POMOperator
+import io.github.pixee.maven.operator.POMScanner
+import io.github.pixee.maven.operator.ProjectModelFactory
+import io.github.pixee.maven.operator.Util.which
+import org.apache.commons.lang3.SystemUtils
+import org.junit.jupiter.api.Assertions.assertFalse
+import org.junit.jupiter.api.Assertions.assertTrue
+import org.junit.jupiter.api.Test
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import java.io.File
+import java.lang.AssertionError
+import java.lang.Exception
+
+data class TestRepo(
+ val slug: String,
+ val branch: String = "master",
+ val pomPath: String = "pom.xml",
+ val useProperties: Boolean = false,
+ val useScanner: Boolean = false,
+ val offline: Boolean = false,
+ val commitId: String? = null,
+) {
+ fun cacheDir() = BASE_CACHE_DIR.resolve("repo-%08X".format(slug.hashCode()))
+
+ companion object {
+ val BASE_CACHE_DIR: File = File(System.getProperty("user.dir") + "/.cache").absoluteFile
+ }
+}
+
+/**
+ * This test actually picks up several github repos and apply the POM Operator and checks for a runtime dependency change
+ */
+class MassRepoIT {
+ /*
+ https://github.com/trending/java?since=daily
+
+ apache/pulsar
+ metersphere/metersphere
+ apache/rocketmq
+ OpenAPITools/openapi-generator
+ casbin/jcasbin
+ trinodb/trino
+ bytedeco/javacv
+ flowable/flowable-engine
+
+ Azure/azure-sdk-for-java
+ questdb/questdb
+ */
+
+ private val repos = listOf(
+ TestRepo(
+ "WebGoat/WebGoat",
+ useProperties = true,
+ branch = "main",
+ useScanner = false,
+ commitId = "e75cfbeb110e3d3a2ca3c8fee2754992d89c419d",
+ pomPath = "webgoat-lessons/xxe/pom.xml",
+ ) to "io.github.pixee:java-security-toolkit:1.0.2",
+ TestRepo(
+ "WebGoat/WebGoat",
+ useProperties = true,
+ branch = "main",
+ useScanner = true,
+ offline = true,
+ pomPath = "webgoat-container/pom.xml"
+ ) to "io.github.pixee:java-security-toolkit:1.0.2",
+ TestRepo(
+ "WebGoat/WebGoat",
+ useProperties = true,
+ branch = "main",
+ useScanner = false,
+ pomPath = "webgoat-container/pom.xml"
+ ) to "io.github.pixee:java-security-toolkit:1.0.2",
+ TestRepo(
+ "WebGoat/WebGoat",
+ useProperties = true,
+ branch = "main",
+ pomPath = "webgoat-container/pom.xml"
+ ) to "io.github.pixee:java-security-toolkit:1.0.2",
+ TestRepo(
+ "CRRogo/vert.x",
+ useProperties = true,
+ ) to "io.github.pixee:java-security-toolkit:1.0.2",
+ TestRepo(
+ "apache/pulsar",
+ pomPath = "pulsar-broker/pom.xml"
+ ) to "commons-codec:commons-codec:1.14",
+ TestRepo(
+ "apache/rocketmq",
+ pomPath = "common/pom.xml"
+ ) to "commons-codec:commons-codec:1.15",
+ TestRepo(
+ "OpenAPITools/openapi-generator",
+ pomPath = "modules/openapi-generator-core/pom.xml"
+ ) to "com.google.guava:guava:31.0-jre",
+ TestRepo(
+ "casbin/jcasbin",
+ ) to "com.google.code.gson:gson:2.8.0",
+ TestRepo(
+ "bytedeco/javacv"
+ ) to "org.jogamp.jocl:jocl-main:2.3.1",
+ )
+
+ /**
+ * Checks out - or resets - a stored github repo
+ */
+ private fun checkoutOrResetCachedRepo(repo: TestRepo) {
+ LOGGER.info("Checkout out $repo into ${repo.cacheDir()}")
+
+ if (!repo.cacheDir().exists()) {
+ // git clone -b branch github.com/slug/ dir
+ val command = arrayOf(
+ "git",
+ "clone",
+ "-b",
+ repo.branch,
+ "https://github.com/${repo.slug}.git",
+ repo.cacheDir().canonicalPath
+ )
+
+ LOGGER.debug("Running command: ${command.joinToString(" ")}")
+
+ val process = ProcessBuilder(*command)
+ .directory(TestRepo.BASE_CACHE_DIR.canonicalFile)
+ .inheritIO()
+ .start()
+
+ process.waitFor()
+ } else {
+ val command = arrayOf("git", "reset", "--hard", "HEAD")
+
+ LOGGER.debug("Running command: ${command.joinToString(" ")}")
+
+ val process = ProcessBuilder(*command)
+ .directory(repo.cacheDir())
+ .inheritIO()
+ .start()
+
+ process.waitFor()
+ }
+
+ if (null != repo.commitId) {
+ val command = arrayOf("git", "checkout", repo.commitId)
+
+ LOGGER.debug("Running command: ${command.joinToString(" ")}")
+
+ val process = ProcessBuilder(*command)
+ .directory(repo.cacheDir())
+ .inheritIO()
+ .start()
+
+ process.waitFor()
+ }
+ }
+
+ /**
+ * Sanity Test on a single repo
+ */
+ @Test
+ fun testBasic() {
+ val firstCase = repos.first()
+
+ testOnRepo(firstCase.first, firstCase.second)
+ }
+
+ /**
+ * THATS THE FULL TEST
+ */
+ @Test
+ fun testAllOthers() {
+ repos.forEachIndexed { n, pair ->
+ try {
+ testOnRepo(pair.first, pair.second)
+ } catch (e: Throwable) {
+ throw AssertionError("while trying example $n of $pair", e)
+ }
+ }
+ }
+
+ private fun testOnRepo(
+ sampleRepo: TestRepo,
+ dependencyToUpgradeString: String
+ ) {
+ LOGGER.info(
+ "Testing on repo {}, branch {} with dependency {} ({})",
+ sampleRepo.slug,
+ sampleRepo.branch,
+ dependencyToUpgradeString,
+ sampleRepo,
+ )
+
+ checkoutOrResetCachedRepo(sampleRepo)
+
+ val originalDependencies = getDependenciesFrom(sampleRepo)
+
+ LOGGER.info("dependencies: {}", originalDependencies)
+
+ val dependencyToUpgrade = Dependency.fromString(dependencyToUpgradeString)
+
+ val projectModelFactory = if (sampleRepo.useScanner) {
+ POMScanner.scanFrom(
+ File(sampleRepo.cacheDir(), sampleRepo.pomPath),
+ sampleRepo.cacheDir()
+ )
+ } else {
+ ProjectModelFactory.Companion.load(File(sampleRepo.cacheDir(), sampleRepo.pomPath))
+ }
+
+ val context = projectModelFactory
+ .withDependency(dependencyToUpgrade)
+ .withSkipIfNewer(false)
+ .withUseProperties(sampleRepo.useProperties)
+ .withOffline(sampleRepo.offline)
+ .build()
+
+ val result = POMOperator.modify(context)
+
+ context.allPomFiles.filter { it.dirty }.forEach {
+ it.file.writeBytes(it.resultPomBytes)
+ }
+
+ val finalDependencies =
+ getDependenciesFrom(sampleRepo)
+
+ LOGGER.info("dependencies: {}", finalDependencies)
+
+ val queryFailed = "" == originalDependencies && "" == finalDependencies
+
+ if (queryFailed) {
+ assertTrue(result, "Must be modified even when query failed")
+ } else {
+ val dependencyAsStringWithPackaging = dependencyToUpgrade.toString()
+
+ assertFalse(
+ originalDependencies.contains(
+ dependencyAsStringWithPackaging
+ ),
+ "Dependency should be originally missing"
+ )
+ assertTrue(
+ finalDependencies.contains(
+ dependencyAsStringWithPackaging
+ ),
+ "New Dependency should be appearing"
+ )
+ }
+ }
+
+ private fun getDependenciesFrom(repo: TestRepo): String = try {
+ getDependenciesFrom(repo.pomPath, repo.cacheDir())
+ } catch (e: Exception) {
+ val pomFile = File(repo.cacheDir(), repo.pomPath)
+
+ val dependencies =
+ POMOperator.queryDependency(
+ POMScanner.scanFrom(pomFile, repo.cacheDir())
+ .withRepositoryPath(repo.cacheDir())
+ .withOffline(false)
+ .build()
+ )
+
+ dependencies.joinToString("\n")
+ }
+
+ private fun getDependenciesFrom(pomPath: String, dir: File): String {
+ val outputFile = File.createTempFile("tmp-pom", ".txt")
+
+ if (outputFile.exists()) {
+ outputFile.delete()
+ }
+
+ val command = if (SystemUtils.IS_OS_WINDOWS) {
+ listOf(which("cmd")!!.canonicalPath, "/c")
+ } else {
+ emptyList()
+ } + listOf(
+ which("mvn")!!.canonicalPath,
+ "-B",
+ "-f",
+ pomPath,
+ "dependency:tree",
+ "-Dscope=runtime",
+ "-DoutputFile=${outputFile.canonicalPath}"
+ )
+
+ LOGGER.debug("Running: " + command.joinToString(" "))
+ LOGGER.debug("Dir: " + dir)
+
+ val process = ProcessBuilder(*command.toTypedArray())
+ .directory(dir)
+ .inheritIO()
+ .start()
+
+ process.waitFor()
+
+ if (! outputFile.exists())
+ return ""
+
+ val result = outputFile.readText()
+
+ outputFile.delete()
+
+ return result
+ }
+
+ companion object {
+ val LOGGER: Logger = LoggerFactory.getLogger(MassRepoIT::class.java)
+ }
+
+ init {
+ /**
+ * Creates the Cache Directory
+ */
+ if (!TestRepo.BASE_CACHE_DIR.exists())
+ TestRepo.BASE_CACHE_DIR.mkdirs()
+ }
+}
diff --git a/pom-operator/src/test/kotlin/io/github/pixee/maven/operator/test/POMOperatorMultipomTest.kt b/pom-operator/src/test/kotlin/io/github/pixee/maven/operator/test/POMOperatorMultipomTest.kt
new file mode 100644
index 000000000..e05398cf0
--- /dev/null
+++ b/pom-operator/src/test/kotlin/io/github/pixee/maven/operator/test/POMOperatorMultipomTest.kt
@@ -0,0 +1,160 @@
+package io.github.pixee.maven.operator.test
+
+import com.google.common.io.Files
+import io.github.pixee.maven.operator.*
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import java.io.File
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+class POMOperatorMultipomTest : AbstractTestBase() {
+ @Test
+ fun testWithParentAndChildMissingPackaging() {
+ val parentResource = getResource("parent-and-child-parent-broken.xml")
+
+ val parentPomFiles = listOf(POMDocumentFactory.load(parentResource))
+
+ val parentPom = ProjectModelFactory.load(
+ parentResource,
+ ).withParentPomFiles(parentPomFiles)
+
+ assertThrows {
+ gwt(
+ "parent-and-child",
+ parentPom.withDependency(Dependency.fromString("org.dom4j:dom4j:2.0.3"))
+ )
+ }
+ }
+
+ @Test
+ fun testWithParentAndChildWrongType() {
+ val parentResource = getResource("parent-and-child-child-broken.xml")
+
+ val parentPomFile = POMDocumentFactory.load(getResource("parent-and-child-parent.xml"))
+
+ val parentPomFiles = listOf(parentPomFile)
+
+ val parentPom = ProjectModelFactory.load(
+ parentResource,
+ ).withParentPomFiles(parentPomFiles)
+
+ assertThrows {
+ gwt(
+ "parent-and-child-wrong-type",
+ parentPom.withDependency(Dependency.fromString("org.dom4j:dom4j:2.0.3"))
+ )
+ }
+ }
+
+ @Test
+ fun testWithMultiplePomsBasicNoVersionProperty() {
+ val parentPomFile = getResource("sample-parent/pom.xml")
+
+ val projectModelFactory = ProjectModelFactory
+ .load(
+ getResource("sample-child-with-relativepath.xml")
+ )
+ .withParentPomFiles(listOf(POMDocumentFactory.load(parentPomFile)))
+ .withUseProperties(false)
+
+ val result = gwt(
+ "multiple-pom-basic-no-version-property",
+ projectModelFactory.withDependency(Dependency.fromString("org.dom4j:dom4j:2.0.3"))
+ )
+
+ validateDepsFrom(result)
+
+ assertTrue(result.allPomFiles.size == 2, "There should be two files")
+ assertTrue(result.allPomFiles.all { it.dirty }, "All files were modified")
+ }
+
+ @Test
+ fun testWithMultiplePomsBasicWithVersionProperty() {
+ val parentPomFile = getResource("sample-parent/pom.xml")
+
+ val sampleChild = getResource("sample-child-with-relativepath.xml")
+
+ val parentPom = ProjectModelFactory.load(
+ sampleChild
+ ).withParentPomFiles(listOf(POMDocumentFactory.load(parentPomFile)))
+ .withUseProperties(true)
+
+ val result = gwt(
+ "multiple-pom-basic-with-version-property",
+ parentPom.withDependency(Dependency.fromString("org.dom4j:dom4j:2.0.3"))
+ )
+
+ validateDepsFrom(result)
+
+ assertTrue(result.allPomFiles.size == 2, "There should be two files")
+ assertTrue(result.allPomFiles.all { it.dirty }, "All files were modified")
+
+ val parentPomString = String(result.parentPomFiles.first().resultPomBytes, Charsets.UTF_8)
+ val pomString = String(result.pomFile.resultPomBytes, Charsets.UTF_8)
+
+ val version = result.dependency!!.version!!
+
+ assertTrue(
+ parentPomString.contains("versions.dom4j>${version}"),
+ "Must contain property with version set on parent pom"
+ )
+ assertFalse(pomString.contains(version), "Must not have reference to version on pom")
+ }
+
+ fun validateDepsFrom(context: ProjectModel) {
+ val resultFiles = copyFiles(context)
+
+ resultFiles.entries.forEach {
+ System.err.println("from ${it.key.pomPath} -> ${it.value}")
+ }
+
+ val pomFile = resultFiles.entries.first().value
+
+ val dependencies = POMOperator.queryDependency(
+ ProjectModelFactory.load(pomFile)
+ .withQueryType(QueryType.UNSAFE)
+ .build()
+ )
+
+ val foundDependency = dependencies.contains(context.dependency!!)
+
+ assertTrue(
+ foundDependency,
+ "Dependency ${context.dependency!!} must be present in context $context ($dependencies)"
+ )
+ }
+
+ fun copyFiles(context: ProjectModel): Map {
+ var commonPath = File(context.pomFile.pomPath!!.toURI()).canonicalFile
+
+ for (p in context.parentPomFiles) {
+ commonPath = File(
+ File(p.pomPath!!.toURI()).canonicalPath.toString()
+ .commonPrefixWith(commonPath.canonicalPath)
+ )
+ }
+
+ val commonPathLen = commonPath.parent.length
+
+ val tmpOutputDir = Files.createTempDir()
+
+ val result = context.allPomFiles.map { p ->
+ val pAsFile = File(p.pomPath!!.toURI())
+
+ val relPath = pAsFile.canonicalPath.substring(1 + commonPathLen)
+
+ val targetPath = File(tmpOutputDir, relPath)
+
+ if (!targetPath.parentFile.exists()) {
+ targetPath.parentFile.mkdirs()
+ }
+
+ targetPath.writeBytes(p.resultPomBytes)
+
+ p to targetPath
+ }.toMap()
+
+ return result
+ }
+}
diff --git a/pom-operator/src/test/kotlin/io/github/pixee/maven/operator/test/POMOperatorQueryTest.kt b/pom-operator/src/test/kotlin/io/github/pixee/maven/operator/test/POMOperatorQueryTest.kt
new file mode 100644
index 000000000..cb079973d
--- /dev/null
+++ b/pom-operator/src/test/kotlin/io/github/pixee/maven/operator/test/POMOperatorQueryTest.kt
@@ -0,0 +1,442 @@
+package io.github.pixee.maven.operator.test
+
+import io.github.pixee.maven.operator.*
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import java.io.File
+import java.lang.IllegalStateException
+import java.nio.file.Files
+
+class POMOperatorQueryTest {
+ companion object {
+ private val LOGGER: Logger = LoggerFactory.getLogger(POMOperatorTest::class.java)
+ }
+
+ @Test
+ fun testBasicQuery() {
+ QueryType.values().filterNot { it == QueryType.NONE }.forEach { queryType ->
+ val context =
+ ProjectModelFactory
+ .load(this.javaClass.getResource("pom-1.xml")!!)
+ .withQueryType(queryType)
+ .build()
+
+ val dependencies = POMOperator.queryDependency(context)
+
+ LOGGER.debug("Dependencies found: {}", dependencies)
+
+ assertTrue(dependencies.isNotEmpty(), "Dependencies are not empty")
+ }
+ }
+
+ @Test
+ fun testFailedSafeQuery() {
+ val context =
+ ProjectModelFactory
+ .load(this.javaClass.getResource("pom-broken.xml")!!)
+ .withQueryType(QueryType.SAFE)
+ .build()
+
+ val dependencies = POMOperator.queryDependency(context)
+
+ assertTrue(dependencies.isEmpty(), "Dependencies are empty")
+ }
+
+ @Test
+ fun testFailedUnsafeQuery() {
+ val context =
+ ProjectModelFactory
+ .load(this.javaClass.getResource("pom-broken.xml")!!)
+ .withQueryType(QueryType.UNSAFE)
+ .build()
+
+ assertThrows { POMOperator.queryDependency(context) }
+ }
+
+ @Test
+ fun testAllQueryTypes() {
+ listOf("pom-1.xml", "pom-3.xml").forEach { pomFile ->
+ Chain.AVAILABLE_QUERY_COMMANDS.forEach {
+ val commandClassName = "io.github.pixee.maven.operator.${it.second}"
+
+ val commandListOverride =
+ listOf(Class.forName(commandClassName).newInstance() as Command)
+
+ val context =
+ ProjectModelFactory
+ .load(this.javaClass.getResource(pomFile)!!)
+ .withQueryType(QueryType.UNSAFE)
+ .build()
+
+ val dependencies =
+ POMOperator.queryDependency(context, commandList = commandListOverride)
+
+ assertTrue(dependencies.isNotEmpty(), "Dependencies are not empty")
+ }
+ }
+ }
+
+
+ @Test
+ fun testTemporaryDirectory() {
+ QueryType.values().filterNot { it == QueryType.NONE }.forEach { queryType ->
+ val tempDirectory = File("/tmp/mvn-repo-" + System.currentTimeMillis() + ".dir")
+
+ LOGGER.info("Using queryType: $queryType at $tempDirectory")
+
+ assertFalse(tempDirectory.exists(), "Temp Directory does not exist initially")
+ assertEquals(
+ tempDirectory.list()?.filter { File(it).isDirectory }?.size ?: 0,
+ 0,
+ "There must be no files"
+ )
+
+ val context =
+ ProjectModelFactory
+ .load(this.javaClass.getResource("pom-1.xml")!!)
+ .withQueryType(queryType)
+ .withRepositoryPath(tempDirectory)
+ .build()
+
+ val dependencies = POMOperator.queryDependency(context)
+
+ LOGGER.debug("Dependencies found: {}", dependencies)
+
+ assertTrue(dependencies.isNotEmpty(), "Dependencies are not empty")
+
+ assertTrue(tempDirectory.exists(), "Temp Directory ends up existing")
+ assertTrue(tempDirectory.isDirectory, "Temp Directory is a directory")
+ }
+ }
+
+ @Test
+ fun testTemporaryDirectoryAndFullyOffline() {
+ QueryType.values().filterNot { it == QueryType.NONE }.filter { it == QueryType.SAFE }
+ .forEach { queryType ->
+ val tempDirectory = Files.createTempDirectory("mvn-repo").toFile()
+
+ val context =
+ ProjectModelFactory
+ .load(this.javaClass.getResource("pom-1.xml")!!)
+ .withQueryType(queryType)
+ .withRepositoryPath(tempDirectory)
+ .withOffline(true)
+ .build()
+
+ val dependencies = POMOperator.queryDependency(context)
+
+ LOGGER.debug("Dependencies found: {}", dependencies)
+
+ assertTrue(dependencies.isNotEmpty(), "Dependencies are not empty")
+ }
+ }
+
+ @Test
+ fun testOnSyntheticDependency() {
+ val tempDirectory = Files.createTempDirectory("mvn-repo").toFile()
+
+ val tempPom = File(tempDirectory, "pom.xml").toPath()
+
+ val randomName = "random-artifact-" + System.currentTimeMillis()
+
+ Files.write(
+ tempPom, """
+
+
+ 4.0.0
+
+ br.com.ingenieux
+ pom-operator
+ 0.0.1-SNAPSHOT
+
+
+
+ dummyorg
+ ${randomName}
+ 2.1.3
+
+
+
+
+ """.trimIndent().toByteArray()
+ )
+
+ val context =
+ ProjectModelFactory
+ .load(tempPom.toFile())
+ .withQueryType(QueryType.SAFE)
+ .withRepositoryPath(tempDirectory)
+ .withOffline(true)
+ .build()
+
+ val dependencies = POMOperator.queryDependency(context)
+
+ LOGGER.debug("Dependencies found: {}", dependencies)
+
+ assertTrue(dependencies.isNotEmpty(), "Dependencies are not empty")
+
+ assertTrue(dependencies.first().artifactId.equals(randomName), "Random name matches")
+ }
+
+ @Test
+ fun testOnCompositeSyntheticDependency() {
+ val tempDirectory = Files.createTempDirectory("mvn-repo").toFile()
+
+ val tempParentPom = File(tempDirectory, "pom-parent.xml").toPath()
+ val tempPom = File(tempDirectory, "pom.xml").toPath()
+
+ val randomName = "random-artifact-" + System.currentTimeMillis()
+
+ Files.write(
+ tempParentPom, """
+
+ 4.0.0
+
+ somethingelse
+ br.com.ingenieux
+ 1
+
+ pom
+
+
+
+
+ dummyorg
+ managed-${randomName}
+ 1
+
+
+
+
+ """.trim().toByteArray()
+ )
+
+ Files.write(
+ tempPom, """
+
+
+ 4.0.0
+
+ br.com.ingenieux
+ pom-operator
+ 0.0.1-SNAPSHOT
+
+
+ somethingelse
+ br.com.ingenieux
+ 1
+ ./pom-parent.xml
+
+
+
+
+ dummyorg
+ ${randomName}
+ 2.1.3
+
+
+ dummyorg
+ managed-${randomName}
+
+
+
+
+ """.trim().toByteArray()
+ )
+
+ val context =
+ ProjectModelFactory
+ .load(tempPom.toFile())
+ .withQueryType(QueryType.SAFE)
+ .withRepositoryPath(tempDirectory)
+ .withOffline(true)
+ .build()
+
+ val dependencies = POMOperator.queryDependency(context)
+
+ LOGGER.debug("Dependencies found: {}", dependencies)
+
+ assertTrue(dependencies.isNotEmpty(), "Dependencies are not empty")
+
+ assertTrue(dependencies.first().artifactId.equals(randomName), "Random name matches")
+ }
+
+ @Test
+ fun testOnCompositeSyntheticDependencyIncompleteWithoutParsing() {
+ val tempDirectory = Files.createTempDirectory("mvn-repo").toFile()
+
+ val tempPom = File(tempDirectory, "pom.xml").toPath()
+
+ val randomName = "random-artifact-" + System.currentTimeMillis()
+
+ Files.write(
+ tempPom, """
+
+
+ 4.0.0
+
+ br.com.ingenieux
+ pom-operator
+ 0.0.1-SNAPSHOT
+
+
+ somethingelse
+ br.com.ingenieux
+ 1
+ ./pom-parent.xml
+
+
+
+
+ dummyorg
+ ${randomName}
+ 2.1.3
+
+
+ dummyorg
+ managed-${randomName}
+
+
+
+
+ """.trim().toByteArray()
+ )
+
+ val context =
+ ProjectModelFactory
+ .load(tempPom.toFile())
+ .withQueryType(QueryType.SAFE)
+ .withRepositoryPath(tempDirectory)
+ .withOffline(true)
+ .build()
+
+ val dependencies = POMOperator.queryDependency(
+ context,
+ commandList = getCommandListFor("QueryByEmbedder", "QueryByResolver")
+ )
+
+ LOGGER.debug("Dependencies found: {}", dependencies)
+
+ assertTrue(dependencies.isEmpty(), "Dependencies are empty")
+ }
+
+ @Test
+ fun testOnCompositeSyntheticDependencyIncompleteButWithParser() {
+ val tempDirectory = Files.createTempDirectory("mvn-repo").toFile()
+
+ val tempPom = File(tempDirectory, "pom.xml").toPath()
+
+ val randomName = "random-artifact-" + System.currentTimeMillis()
+
+ Files.write(
+ tempPom, """
+
+
+ 4.0.0
+
+ br.com.ingenieux
+ pom-operator
+ 0.0.1-SNAPSHOT
+
+
+ somethingelse
+ br.com.ingenieux
+ 1
+ ./pom-parent.xml
+
+
+
+
+
+ dummyorg
+ managed-${randomName}
+ 0.0.1
+
+
+
+
+
+
+ dummyorg
+ ${randomName}
+ 2.1.3
+
+
+ dummyorg
+ managed-${randomName}
+
+
+
+
+ """.trim().toByteArray()
+ )
+
+ val context =
+ ProjectModelFactory
+ .load(tempPom.toFile())
+ .withQueryType(QueryType.SAFE)
+ .withRepositoryPath(tempDirectory)
+ .withOffline(true)
+ .build()
+
+ val dependencies =
+ POMOperator.queryDependency(context, commandList = getCommandListFor("QueryByParsing"))
+
+ LOGGER.debug("Dependencies found: {}", dependencies)
+
+ assertTrue(dependencies.isNotEmpty(), "Dependencies are empty")
+
+ val foundDependencies =
+ dependencies.firstOrNull { it.version == "0.0.1" && it.artifactId.equals("managed-$randomName") }
+
+ assertTrue(foundDependencies != null, "there's a dependencyManaged-version")
+ }
+
+ private fun getCommandListFor(vararg names: String): List =
+ names.map {
+ val commandClassName = "io.github.pixee.maven.operator.${it}"
+
+ val commandInstance =
+ Class.forName(commandClassName).newInstance() as Command
+
+ commandInstance
+ }.toList()
+
+ @Test
+ fun testOfflineQueryResolution() {
+ val tempDirectory = Files.createTempDirectory("mvn-repo").toFile()
+
+ val context =
+ ProjectModelFactory
+ .load(javaClass.getResource("nested/child/pom/pom-3-child.xml"))
+ .withQueryType(QueryType.SAFE)
+ .withRepositoryPath(tempDirectory)
+ .withOffline(true)
+ .build()
+
+ val dependencies = POMOperator.queryDependency(context)
+
+ LOGGER.debug("Dependencies found: {}", dependencies)
+
+ assertTrue(dependencies.isEmpty(), "Dependencies are empty")
+
+ }
+}
diff --git a/pom-operator/src/test/kotlin/io/github/pixee/maven/operator/test/POMOperatorTest.kt b/pom-operator/src/test/kotlin/io/github/pixee/maven/operator/test/POMOperatorTest.kt
new file mode 100644
index 000000000..a7ab08234
--- /dev/null
+++ b/pom-operator/src/test/kotlin/io/github/pixee/maven/operator/test/POMOperatorTest.kt
@@ -0,0 +1,456 @@
+package io.github.pixee.maven.operator.test
+
+import io.github.pixee.maven.operator.*
+import io.github.pixee.maven.operator.Util.buildLookupExpressionForDependency
+import io.github.pixee.maven.operator.Util.selectXPathNodes
+import io.github.pixee.maven.operator.Util.which
+import org.apache.commons.lang3.SystemUtils
+import org.dom4j.DocumentException
+import org.hamcrest.MatcherAssert.assertThat
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import org.xmlunit.diff.ComparisonType
+import java.io.File
+import java.nio.charset.Charset
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+
+/**
+ * Unit test for simple App.
+ */
+class POMOperatorTest : AbstractTestBase() {
+ @Test
+ fun testWithBrokenPom() {
+ assertThrows {
+ gwt(
+ "broken-pom",
+ ProjectModelFactory.load(
+ POMOperatorTest::class.java.getResource("broken-pom.xml")!!,
+ ).withDependency(Dependency.fromString("org.dom4j:dom4j:2.0.3"))
+ )
+ }
+ }
+
+ @Test
+ fun testWithMultipleDependencies() {
+ val deps = listOf(
+ "org.slf4j:slf4j-api:1.7.25",
+ "io.github.pixee:java-code-security-toolkit:1.0.2",
+ "org.owasp.encoder:encoder:1.2.3",
+ ).map { Dependency.fromString(it) }.toList()
+
+ val testPom = File.createTempFile("pom", ".xml")
+
+ POMOperatorTest::class.java.getResourceAsStream("sample-bad-pom.xml")!!
+ .copyTo(testPom.outputStream())
+
+ deps.forEach { d ->
+ val projectModel = ProjectModelFactory.load(testPom)
+ .withDependency(d)
+ .withUseProperties(true)
+ .withOverrideIfAlreadyExists(true)
+ .build()
+
+ if (POMOperator.modify(projectModel)) {
+ assertTrue(projectModel.pomFile.dirty, "Original POM File is Dirty")
+
+ val resultPomAsXml = String(projectModel.pomFile.resultPomBytes)
+
+ LOGGER.debug("resultPomAsXml: {}", resultPomAsXml)
+
+ testPom.writeBytes(projectModel.pomFile.resultPomBytes)
+ } else {
+ throw IllegalStateException("Code that shouldn't be reached out at all")
+ }
+ }
+
+ val resolvedDeps = POMOperator.queryDependency(
+ ProjectModelFactory.load(testPom).withQueryType(QueryType.SAFE).build()
+ )
+
+ val testPomContents = testPom.readText()
+
+ assertTrue(3 == resolvedDeps.size, "Must have three dependencies")
+ assertTrue(testPomContents.contains(""),
+ "There must be an empty element with three spaces inside a comment"
+ )
+ }
+
+ @Test
+ fun testCaseWithProperty() {
+ val dependencyToUpgrade =
+ Dependency("org.dom4j", "dom4j", version = "1.0.0")
+
+ val context = gwt(
+ "case-with-property",
+ ProjectModelFactory.load(
+ POMOperatorTest::class.java.getResource("pom-with-property-simple.xml")!!,
+ ).withDependency(dependencyToUpgrade).withUseProperties(true).withSkipIfNewer(true)
+ )
+
+ LOGGER.debug("original pom: {}", context.pomFile.pomDocument.asXML())
+ LOGGER.debug("resulting pom: {}", context.pomFile.resultPom.asXML())
+
+ assertTrue(context.pomFile.dirty, "Original POM File is Dirty")
+
+ val diff = getXmlDifferences(context.pomFile.pomDocument, context.pomFile.resultPom)
+
+ assertThat("Document has differences", diff.hasDifferences())
+
+ val differenceList = diff.differences.toList()
+
+ assertThat("Document has one difference", 1 == differenceList.size)
+
+ assertThat(
+ "Document changes a single version",
+ differenceList.first().toString()
+ .startsWith("Expected text value '0.0.1-SNAPSHOT' but was '1.0.0'")
+ )
+
+ assertEquals(
+ differenceList.first().comparison.testDetails.xPath,
+ "/project[1]/properties[1]/sample.version[1]/text()[1]",
+ "Document changes a property called 'sample.version'"
+ )
+ }
+
+ @Test
+ fun testCaseWithPropertyDefinedTwice() {
+ val dependencyToUpgrade =
+ Dependency("org.dom4j", "dom4j", version = "1.0.0")
+
+ val originalPom = """
+
+
+ 4.0.0
+
+ br.com.ingenieux
+ pom-operator
+ 0.0.1-SNAPSHOT
+
+
+ 0.0.1
+
+
+
+
+ org.dom4j
+ dom4j
+ ${'$'}{dom4j.version}
+
+
+ org.dom4j
+ dom4j-other
+ ${'$'}{dom4j.version}
+
+
+
+ """.trimIndent()
+ val context =
+ ProjectModelFactory.load(
+ originalPom.byteInputStream(),
+ ).withDependency(dependencyToUpgrade).withUseProperties(true)
+ .withOverrideIfAlreadyExists(false)
+ .build()
+
+ assertThrows {
+ POMOperator.modify(context)
+ }
+ }
+
+ @Test
+ fun testCaseWithoutPropertyButDefiningOne() {
+ val dependencyToUpgrade =
+ Dependency("org.dom4j", "dom4j", version = "1.0.0")
+
+ val originalPom = """
+
+
+ 4.0.0
+
+ br.com.ingenieux
+ pom-operator
+ 0.0.1-SNAPSHOT
+
+
+
+ org.dom4j
+ dom4j
+ 0.0.1-SNAPSHOT
+
+
+
+ """.trim()
+ val context =
+ ProjectModelFactory.load(
+ originalPom.byteInputStream(),
+ ).withDependency(dependencyToUpgrade).withUseProperties(true).withSkipIfNewer(true)
+ .build()
+
+ POMOperator.modify(context)
+
+ assertTrue(context.pomFile.dirty, "Original POM File is Dirty")
+
+ LOGGER.debug("original pom: {}", context.pomFile.pomDocument.asXML())
+ LOGGER.debug("resulting pom: {}", context.pomFile.resultPom.asXML())
+
+ val diff = getXmlDifferences(context.pomFile.pomDocument, context.pomFile.resultPom)
+
+ assertThat("Document has differences", diff.hasDifferences())
+
+ val differencesAsList = diff.differences.toList()
+
+ assertThat("Document has several differences", differencesAsList.size > 1)
+ }
+
+ @Test
+ fun testFileWithTabs() {
+ val dependencyToUpgrade =
+ Dependency("org.dom4j", "dom4j", version = "1.0.0")
+
+ val originalPom =
+ "\n\n\t4.0.0\n\t\n\t\tbuild-utils\n\t\torg.modafocas.mojo\n\t\t0.0.1-SNAPSHOT\n\t\t../pom.xml\n\t\n\n\tderby-maven-plugin\n\tmaven-plugin\n\n\t\n\t\t\n\t\t\torg.apache.maven\n\t\t\tmaven-plugin-api\n\t\t\t2.0\n\t\t\n\t\t\n\t\t\tjunit\n\t\t\tjunit\n\t\t\t3.8.1\n\t\t\ttest\n\t\t\n\t\t\n\t\t\torg.apache.derby\n\t\t\tderby\n\t\t\t\${derbyVersion}\n\t\t\n\t\t\n\t\t\torg.apache.derby\n\t\t\tderbynet\n\t\t\t\${derbyVersion}\n\t\t\n\t\t\n\t\t\torg.apache.derby\n\t\t\tderbyclient\n\t\t\t\${derbyVersion}\n\t\t\n\t\t\n\t\t\tcommons-io\n\t\t\tcommons-io\n\t\t\t1.4\n\t\t\tjar\n\t\t\tcompile\n\t\t\n\t\n\n\t\n\t\t10.6.2.1\n\t\n\n"
+
+ val context =
+ ProjectModelFactory.load(
+ originalPom.byteInputStream(),
+ ).withDependency(dependencyToUpgrade).withUseProperties(true).withSkipIfNewer(true)
+ .build()
+
+ POMOperator.modify(context)
+
+ val resultPom = context.pomFile.resultPomBytes.toString(Charset.defaultCharset())
+
+ assertTrue(context.pomFile.dirty, "Original POM File is Dirty")
+
+ // aldrin: uncomment this to check out formatting - useful for the next section
+ // println(StringEscapeUtils.escapeJava(resultPom))
+
+ assertThat(
+ "Document should have a tab-based string",
+ resultPom.contains("\n\t\t\n\t\t\torg.dom4j\n\t\t\tdom4j\n\t\t\n")
+ )
+ }
+
+}
diff --git a/pom-operator/src/test/kotlin/io/github/pixee/maven/operator/test/POMScannerTest.kt b/pom-operator/src/test/kotlin/io/github/pixee/maven/operator/test/POMScannerTest.kt
new file mode 100644
index 000000000..c6eb2add1
--- /dev/null
+++ b/pom-operator/src/test/kotlin/io/github/pixee/maven/operator/test/POMScannerTest.kt
@@ -0,0 +1,106 @@
+package io.github.pixee.maven.operator.test
+
+import io.github.pixee.maven.operator.InvalidPathException
+import io.github.pixee.maven.operator.POMScanner
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.assertThrows
+import java.io.File
+import kotlin.test.assertTrue
+import kotlin.test.fail
+
+class POMScannerTest: AbstractTestBase() {
+ val currentDirectory = File(System.getProperty("user.dir"))
+
+ @Test
+ fun testBasic() {
+ val pomFile = getResourceAsFile("sample-child-with-relativepath.xml")
+
+ val pmf = POMScanner.scanFrom(pomFile, currentDirectory)
+ }
+
+ @Test
+ fun testTwoLevelsWithLoop() {
+ val pomFile = getResourceAsFile("sample-child-with-relativepath-and-two-levels.xml")
+
+ assertThrows { POMScanner.scanFrom(pomFile, currentDirectory) }
+ }
+
+ @Test
+ fun testTwoLevelsWithoutLoop() {
+ val pomFile = getResourceAsFile("sample-child-with-relativepath-and-two-levels-nonloop.xml")
+
+ val pmf = POMScanner.scanFrom(pomFile, currentDirectory).build()
+
+ assertTrue(pmf.parentPomFiles.size == 2, "There must be two parent pom files")
+
+ val uniquePaths = pmf.allPomFiles.map { it.pomPath!!.toURI().normalize().toString() }.toSet()
+
+ val uniquePathsAsString = uniquePaths.joinToString(" ")
+
+ LOGGER.info("uniquePathsAsString: $uniquePathsAsString")
+
+ assertTrue(uniquePaths.size == 3, "There must be three unique pom files referenced")
+ }
+
+ @Test
+ fun testMultipleChildren() {
+ for (index in 1..3) {
+ val pomFile = getResourceAsFile("nested/child/pom/pom-$index-child.xml")
+
+ val pm = POMScanner.scanFrom(pomFile, currentDirectory).build()
+
+ assertTrue(pm.parentPomFiles.size == 2, "There must be two parent pom files")
+
+ val uniquePaths = pm.allPomFiles.map { it.pomPath!!.toURI().normalize().toString() }
+
+ val uniquePathsAsString = uniquePaths.joinToString(" ")
+
+ LOGGER.info("uniquePathsAsString: $uniquePathsAsString")
+
+ assertTrue(uniquePaths.size == 3, "There must be three unique pom files referenced")
+ }
+ }
+
+ @Test
+ fun testInvalidRelativePaths() {
+ for (index in 1..3) {
+ val name = "sample-child-with-broken-path-${index}.xml"
+ val pomFile = getResourceAsFile(name)
+
+ try {
+ POMScanner.scanFrom(pomFile, currentDirectory)
+
+ fail("Unreachable code for file: $name")
+ } catch (e: Exception) {
+ LOGGER.info("Exception thrown: ", e)
+
+ if (e is InvalidPathException) {
+ continue
+ }
+
+ throw e
+ }
+ }
+ }
+
+ @Test
+ fun testWithRelativePathEmpty() {
+ for (index in 3..4) {
+ val pomFile = getResourceAsFile("pom-multiple-pom-parent-level-${index}.xml")
+
+ try {
+ val pmf = POMScanner.scanFrom(pomFile, currentDirectory)
+
+ assertTrue(pmf.build().parentPomFiles.isNotEmpty())
+ } catch (e: InvalidPathException) {
+ LOGGER.info("Exception thrown: ", e)
+
+ if (e is InvalidPathException) {
+ continue
+ }
+
+ throw e
+ }
+ }
+ }
+}
diff --git a/pom-operator/src/test/kotlin/io/github/pixee/maven/operator/test/PropertyResolutionTest.kt b/pom-operator/src/test/kotlin/io/github/pixee/maven/operator/test/PropertyResolutionTest.kt
new file mode 100644
index 000000000..2f7cc96ec
--- /dev/null
+++ b/pom-operator/src/test/kotlin/io/github/pixee/maven/operator/test/PropertyResolutionTest.kt
@@ -0,0 +1,52 @@
+package io.github.pixee.maven.operator.test
+
+import io.github.pixee.maven.operator.Dependency
+import io.github.pixee.maven.operator.ProjectModelFactory
+import org.junit.jupiter.api.Assertions
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Assertions.assertTrue
+import org.junit.jupiter.api.Test
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+class PropertyResolutionTest {
+ @Test
+ fun testPropertyResolutionWhenProfileIsDeactivatedForcefully() {
+ val resolvedProperties = resolveWithProfiles("!test-profile")
+
+ Assertions.assertFalse(resolvedProperties.contains("foo"), "foo property must not be there")
+ }
+
+ @Test
+ fun testPropertyResolutionWhenProfileIsMissing() {
+ val resolvedProperties = resolveWithProfiles()
+
+ Assertions.assertFalse(resolvedProperties.contains("foo"), "foo property must not be there")
+ }
+
+ @Test
+ fun testPropertyResolutionWhenProfileIsActivated() {
+ val resolvedProperties = resolveWithProfiles("test-profile")
+
+ assertTrue(resolvedProperties.contains("foo"), "foo property must be there")
+ assertEquals(resolvedProperties["foo"], "bar", "foo property must be equal to 'bar'")
+ }
+
+ private fun resolveWithProfiles(vararg profilesToUse: String): Map {
+ LOGGER.debug("resolving with profiles: {}", profilesToUse)
+
+ val dependencyToUpgrade = Dependency("org.dom4j", "dom4j", version = "2.0.2")
+ val context =
+ ProjectModelFactory.load(
+ POMOperatorTest::class.java.getResource("pom-1.xml")!!,
+ ).withDependency(dependencyToUpgrade).withActiveProfiles(*profilesToUse).build()
+
+ LOGGER.debug("Resolved Properties: {}", context.resolvedProperties)
+
+ return context.resolvedProperties
+ }
+
+ companion object {
+ private val LOGGER: Logger = LoggerFactory.getLogger(POMOperatorTest::class.java)
+ }
+}
diff --git a/pom-operator/src/test/kotlin/io/github/pixee/maven/operator/test/util.kt b/pom-operator/src/test/kotlin/io/github/pixee/maven/operator/test/util.kt
new file mode 100644
index 000000000..43669f4f6
--- /dev/null
+++ b/pom-operator/src/test/kotlin/io/github/pixee/maven/operator/test/util.kt
@@ -0,0 +1,63 @@
+package io.github.pixee.maven.operator.test
+
+import io.github.pixee.maven.operator.ProjectModel
+import io.github.pixee.maven.operator.Util.which
+import org.apache.commons.lang3.SystemUtils
+import org.dom4j.Document
+import org.dom4j.io.SAXReader
+import java.io.File
+import java.io.FileInputStream
+
+
+internal fun ProjectModel.getRuntimeResolvedProperties(): Map =
+ this.getEffectivePom().rootElement.elements("properties").flatMap { it.elements() }
+ .associate {
+ it.name to it.text
+ }
+
+fun ProjectModel.getEffectivePom(): Document {
+ val tmpInputFile = File.createTempFile("tmp-pom-orig", ".xml")
+
+ tmpInputFile.writeBytes(this.pomFile.resultPomBytes)
+
+ val tmpOutputFile = File.createTempFile("tmp-pom", ".xml")
+
+ val processArgs: MutableList =
+ mutableListOf(
+ which("mvn")!!.absolutePath,
+ "-B",
+ "-N",
+ "-f",
+ tmpInputFile.absolutePath,
+ )
+
+ if (SystemUtils.IS_OS_WINDOWS) {
+ processArgs.addAll(0, listOf("cmd.exe", "/c"))
+ }
+
+ if (this.activeProfiles.isNotEmpty()) {
+ // TODO Aldrin: How safe is not to escape those things? My concern is that deactivating a profile uses '!',
+ // and I'm not sure how shell escaping rules play a part on that
+ processArgs.addAll(listOf("-P", this.activeProfiles.joinToString(",")))
+ }
+
+ processArgs.addAll(
+ listOf(
+ "help:effective-pom",
+ "-Doutput=${tmpOutputFile.absolutePath}"
+ )
+ )
+
+ val psBuilder = ProcessBuilder(processArgs).inheritIO()
+
+ psBuilder.environment().putAll(System.getenv())
+
+ val process = psBuilder.start()
+
+ val retCode = process.waitFor()
+
+ if (0 != retCode)
+ throw IllegalStateException("Unexpected return code from maven: $retCode")
+
+ return SAXReader().read(FileInputStream(tmpOutputFile))
+}
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/broken-pom.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/broken-pom.xml
new file mode 100644
index 000000000..b50e5c53b
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/broken-pom.xml
@@ -0,0 +1,3 @@
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/nested/child/pom/pom-1-child.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/nested/child/pom/pom-1-child.xml
new file mode 100644
index 000000000..e4a3f45d9
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/nested/child/pom/pom-1-child.xml
@@ -0,0 +1,16 @@
+
+
+ 4.0.0
+
+
+ br.com.ingenieux
+ org-pom
+ 1
+ ./pom-1.xml
+
+
+ pom-1-child
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/nested/child/pom/pom-1.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/nested/child/pom/pom-1.xml
new file mode 100644
index 000000000..1381aa96c
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/nested/child/pom/pom-1.xml
@@ -0,0 +1,17 @@
+
+
+ 4.0.0
+
+
+ br.com.ingenieux
+ super-pom
+ 1
+ ../../super/pom/pom.xml
+
+
+ org-pom
+ pom
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/nested/child/pom/pom-2-child.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/nested/child/pom/pom-2-child.xml
new file mode 100644
index 000000000..777615260
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/nested/child/pom/pom-2-child.xml
@@ -0,0 +1,16 @@
+
+
+ 4.0.0
+
+
+ br.com.ingenieux
+ org-pom
+ 1
+ ./pom-2.xml
+
+
+ pom-2-child
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/nested/child/pom/pom-2.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/nested/child/pom/pom-2.xml
new file mode 100644
index 000000000..48050c8c6
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/nested/child/pom/pom-2.xml
@@ -0,0 +1,17 @@
+
+
+ 4.0.0
+
+
+ br.com.ingenieux
+ super-pom
+ 1
+ ../../super/pom
+
+
+ org-pom
+ pom
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/nested/child/pom/pom-3-child.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/nested/child/pom/pom-3-child.xml
new file mode 100644
index 000000000..4e887529c
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/nested/child/pom/pom-3-child.xml
@@ -0,0 +1,16 @@
+
+
+ 4.0.0
+
+
+ br.com.ingenieux
+ org-pom
+ 1
+ ./pom-3.xml
+
+
+ pom-3-child
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/nested/child/pom/pom-3.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/nested/child/pom/pom-3.xml
new file mode 100644
index 000000000..d64713cd7
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/nested/child/pom/pom-3.xml
@@ -0,0 +1,17 @@
+
+
+ 4.0.0
+
+
+ br.com.ingenieux
+ super-pom
+ 1
+ ../../super/pom/other-pom.xml
+
+
+ org-pom
+ pom
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/nested/super/pom/other-pom.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/nested/super/pom/other-pom.xml
new file mode 100644
index 000000000..59d61dcba
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/nested/super/pom/other-pom.xml
@@ -0,0 +1,13 @@
+
+
+ 4.0.0
+
+ br.com.ingenieux
+ 1
+ super-pom
+
+ pom
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/nested/super/pom/pom.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/nested/super/pom/pom.xml
new file mode 100644
index 000000000..5b9c4e081
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/nested/super/pom/pom.xml
@@ -0,0 +1,13 @@
+
+
+ 4.0.0
+
+ br.com.ingenieux
+ 1
+ org-pom
+
+ pom
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/parent-and-child-child-broken.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/parent-and-child-child-broken.xml
new file mode 100644
index 000000000..cd59a5c84
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/parent-and-child-child-broken.xml
@@ -0,0 +1,18 @@
+
+
+ 4.0.0
+
+ br.com.ingenieux
+ pom-operator
+ 0.0.1-SNAPSHOT
+
+
+ UTF-8
+ 1.8
+ 1.8
+ 1.5.31
+
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/parent-and-child-parent-broken.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/parent-and-child-parent-broken.xml
new file mode 100644
index 000000000..c12b8458f
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/parent-and-child-parent-broken.xml
@@ -0,0 +1,16 @@
+
+
+ 4.0.0
+
+
+ br.com.ingenieux
+ pom-operator
+ 1
+ ./parent-and-child-parent.xml
+
+
+ pom-operator
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/parent-and-child-parent.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/parent-and-child-parent.xml
new file mode 100644
index 000000000..e27c9a221
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/parent-and-child-parent.xml
@@ -0,0 +1,19 @@
+
+
+ 4.0.0
+
+ br.com.ingenieux
+ pom-operator
+ 1
+ pom
+
+
+ UTF-8
+ 1.8
+ 1.8
+ 1.5.31
+
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-1.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-1.xml
new file mode 100644
index 000000000..7b8471751
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-1.xml
@@ -0,0 +1,159 @@
+
+
+ 4.0.0
+
+ br.com.ingenieux
+ pom-operator
+ 0.0.1-SNAPSHOT
+
+
+ UTF-8
+ 1.8
+ 1.8
+ 1.5.31
+
+
+
+
+ test-profile
+
+ bar
+
+
+
+
+
+
+ org.dom4j
+ dom4j
+ 2.1.3
+
+
+ jaxen
+ jaxen
+ 1.2.0
+
+
+ xerces
+ xercesImpl
+ 2.12.1
+
+
+ io.github.java-diff-utils
+ java-diff-utils
+ 4.9
+
+
+ junit
+ junit
+ 4.11
+ test
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jdk8
+ ${kotlin.version}
+
+
+ org.jetbrains.kotlin
+ kotlin-test
+ ${kotlin.version}
+ test
+
+
+
+
+
+
+
+
+ maven-clean-plugin
+ 3.1.0
+
+
+
+ maven-resources-plugin
+ 3.0.2
+
+
+ maven-compiler-plugin
+ 3.8.0
+
+
+ maven-surefire-plugin
+ 2.22.1
+
+
+ maven-jar-plugin
+ 3.0.2
+
+
+ maven-install-plugin
+ 2.5.2
+
+
+ maven-deploy-plugin
+ 2.8.2
+
+
+
+ maven-site-plugin
+ 3.7.1
+
+
+ maven-project-info-reports-plugin
+ 3.0.0
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+ ${kotlin.version}
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ test-compile
+ test-compile
+
+ test-compile
+
+
+
+
+ 1.8
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ testCompile
+ test-compile
+
+ testCompile
+
+
+
+
+
+
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-3.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-3.xml
new file mode 100644
index 000000000..80ded0522
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-3.xml
@@ -0,0 +1,509 @@
+
+ 4.0.0
+
+
+ org.codehaus.mojo
+ mojo-parent
+ 63
+
+
+ versions-maven-plugin
+ 2.9-SNAPSHOT
+ maven-plugin
+
+ Versions Maven Plugin
+ http://www.mojohaus.org/versions-maven-plugin/
+
+ Versions Plugin for Maven. The Versions Plugin updates the versions of components in the
+ POM.
+
+ 2008
+
+
+ Apache License, Version 2.0
+ https://www.apache.org/licenses/LICENSE-2.0.txt
+ repo
+
+
+
+
+
+ Stephen Connolly
+ stephenconnolly at codehaus
+
+ Lead Developer
+
+ 0
+
+
+ Paul Gier
+ pgier at redhat
+
+ Developer
+
+
+
+ Arnaud Heritier
+ aheritier at apache
+
+ Developer
+
+ +1
+
+
+
+
+ Benoit Lafontaine
+ +1
+
+
+ Martin Franklin
+
+
+ Tom Folga
+
+
+ Eric Pabst
+
+
+ Stefan Seelmann
+
+
+ Clement Denis
+
+
+ Erik Schepers
+
+
+ Anton Johansson
+ antoon.johansson@gmail.com
+ +1
+
+
+
+
+ 3.0
+
+
+
+ scm:git:https://github.com/mojohaus/versions-maven-plugin.git
+ scm:git:ssh://git@github.com/mojohaus/versions-maven-plugin.git
+
+ https://github.com/mojohaus/versions-maven-plugin/tree/${project.scm.tag}
+ versions-maven-plugin-2.8.1
+
+
+ github
+ https://github.com/mojohaus/versions-maven-plugin/issues/
+
+
+
+ 1.8
+ ${mojo.java.target}
+ 5.8.1
+ 3.0.5
+ 3.4.0
+ 1.10
+ 1.10
+ ${project.version}
+ 3.7
+ 2020-08-07T21:31:00Z
+
+
+
+
+
+ org.junit
+ junit-bom
+ ${junitBomVersion}
+ pom
+ import
+
+
+
+
+
+
+ org.apache.maven.plugin-tools
+ maven-plugin-annotations
+ 3.6.1
+ provided
+
+
+
+ org.apache.maven
+ maven-artifact
+ ${mavenVersion}
+
+
+ org.apache.maven
+ maven-core
+ ${mavenVersion}
+
+
+ org.apache.maven
+ maven-compat
+ ${mavenVersion}
+
+
+ org.apache.maven
+ maven-model
+ ${mavenVersion}
+
+
+ org.apache.maven
+ maven-plugin-api
+ ${mavenVersion}
+
+
+ org.apache.maven
+ maven-settings
+ ${mavenVersion}
+
+
+ org.apache.maven.reporting
+ maven-reporting-api
+ 3.0
+
+
+ org.apache.maven.reporting
+ maven-reporting-impl
+ 3.0.0
+
+
+ org.apache.maven.shared
+ maven-common-artifact-filters
+ 3.2.0
+
+
+ org.apache.maven.wagon
+ wagon-provider-api
+ ${wagonVersion}
+
+
+ org.apache.maven.wagon
+ wagon-file
+ ${wagonVersion}
+
+
+
+
+ org.apache.maven.doxia
+ doxia-core
+ ${doxiaVersion}
+
+
+ org.apache.maven.doxia
+ doxia-sink-api
+ ${doxiaVersion}
+
+
+
+
+ org.apache.maven.doxia
+ doxia-site-renderer
+ ${doxia-sitetoolsVersion}
+
+
+ org.codehaus.plexus
+ plexus-container-default
+
+
+ org.codehaus.plexus
+ plexus-component-api
+
+
+
+
+
+ org.codehaus.plexus
+ plexus-utils
+ 3.4.1
+
+
+ org.codehaus.plexus
+ plexus-container-default
+ 2.1.0
+
+
+ org.codehaus.plexus
+ plexus-interactivity-api
+ 1.1
+
+
+ plexus-container-default
+ org.codehaus.plexus
+
+
+
+
+ com.fasterxml.woodstox
+ woodstox-core
+ 6.2.6
+
+
+ org.apache.commons
+ commons-lang3
+ 3.12.0
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
+
+ org.junit.vintage
+ junit-vintage-engine
+ test
+
+
+ org.hamcrest
+ hamcrest-core
+
+
+
+
+ org.mockito
+ mockito-core
+ 3.12.4
+ test
+
+
+ org.hamcrest
+ hamcrest
+ 2.2
+ test
+
+
+
+
+
+
+
+ org.codehaus.modello
+ modello-maven-plugin
+ 1.11
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-plugin-plugin
+
+ true
+
+
+
+
+ helpmojo
+ descriptor
+
+
+
+
+
+ org.codehaus.mojo
+ animal-sniffer-maven-plugin
+
+
+ check-java18
+ test
+
+ check
+
+
+
+ org.codehaus.mojo.signature
+ java18
+ 1.0
+
+
+
+
+
+
+ org.codehaus.modello
+ modello-maven-plugin
+
+
+ generate-sources
+ generate-sources
+
+
+ xpp3-reader
+
+ xpp3-writer
+
+ java
+
+
+
+ site-doc
+ pre-site
+
+ xdoc
+
+
+
+ site-xsd
+ pre-site
+
+ xsd
+
+
+
+ ${project.build.directory}/generated-site/resources/xsd
+
+
+
+
+
+
+ src/main/mdo/rule.mdo
+
+ 2.0.0
+ true
+
+
+
+ org.codehaus.mojo
+ mrm-maven-plugin
+ 1.2.0
+
+
+
+ start
+ stop
+
+
+
+
+ repository.proxy.url
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-invoker-plugin
+
+ src/it
+ ${project.build.directory}/it
+ ${project.build.directory}/local-repo
+ src/it/settings.xml
+ true
+ true
+
+ 1
+
+ */pom.xml
+
+ verify
+
+ ${repository.proxy.url}
+
+ -Xmx256m
+
+
+
+ maven-javadoc-plugin
+
+
+
+ org.codehaus.mojo.versions.model,
+ org.codehaus.mojo.versions.model.io.xpp3
+
+
+
+
+
+
+
+
+
+ maven-javadoc-plugin
+
+
+
+
+
+ maven-invoker-plugin
+
+
+
+ report
+
+
+
+
+
+ org.codehaus.mojo
+ versions-maven-plugin
+ 2.7
+
+
+
+ dependency-updates-report
+ plugin-updates-report
+ property-updates-report
+
+
+
+
+
+
+
+
+
+
+ run-its
+
+ verify
+
+
+ org.apache.maven.plugins
+ maven-invoker-plugin
+
+
+ integration-test
+
+ install
+ run
+
+
+ false
+ true
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-invoker-plugin
+
+
+
+ report
+
+
+
+
+
+
+
+
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-broken.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-broken.xml
new file mode 100644
index 000000000..58952de39
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-broken.xml
@@ -0,0 +1,5 @@
+
+
+ missing
+ 4.0.0
+
\ No newline at end of file
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-1-result.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-1-result.xml
new file mode 100644
index 000000000..ff03447ec
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-1-result.xml
@@ -0,0 +1,31 @@
+
+ 4.0.0
+
+ br.com.ingenieux
+ pom-operator
+ 0.0.1-SNAPSHOT
+
+
+ UTF-8
+ 1.8
+ 1.8
+ 1.5.31
+
+
+
+
+ org.dom4j
+ dom4j
+ 2.0.3
+
+
+
+
+
+ org.dom4j
+ dom4j
+
+
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-1.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-1.xml
new file mode 100644
index 000000000..cb6a84331
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-1.xml
@@ -0,0 +1,16 @@
+
+ 4.0.0
+
+ br.com.ingenieux
+ pom-operator
+ 0.0.1-SNAPSHOT
+
+
+ UTF-8
+ 1.8
+ 1.8
+ 1.5.31
+
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-3-result.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-3-result.xml
new file mode 100644
index 000000000..3e79270b2
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-3-result.xml
@@ -0,0 +1,150 @@
+
+
+ 4.0.0
+
+ br.com.ingenieux
+ pom-operator
+ 0.0.1-SNAPSHOT
+
+
+ UTF-8
+ 1.8
+ 1.8
+ 1.5.31
+
+
+
+
+ org.dom4j
+ dom4j
+ 2.0.2
+
+
+ jaxen
+ jaxen
+ 1.2.0
+
+
+ xerces
+ xercesImpl
+ 2.12.1
+
+
+ io.github.java-diff-utils
+ java-diff-utils
+ 4.9
+
+
+ junit
+ junit
+ 4.11
+ test
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jdk8
+ ${kotlin.version}
+
+
+ org.jetbrains.kotlin
+ kotlin-test
+ ${kotlin.version}
+ test
+
+
+
+
+
+
+
+
+ maven-clean-plugin
+ 3.1.0
+
+
+
+ maven-resources-plugin
+ 3.0.2
+
+
+ maven-compiler-plugin
+ 3.8.0
+
+
+ maven-surefire-plugin
+ 2.22.1
+
+
+ maven-jar-plugin
+ 3.0.2
+
+
+ maven-install-plugin
+ 2.5.2
+
+
+ maven-deploy-plugin
+ 2.8.2
+
+
+
+ maven-site-plugin
+ 3.7.1
+
+
+ maven-project-info-reports-plugin
+ 3.0.0
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+ ${kotlin.version}
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ test-compile
+ test-compile
+
+ test-compile
+
+
+
+
+ 1.8
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ testCompile
+ test-compile
+
+ testCompile
+
+
+
+
+
+
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-3.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-3.xml
new file mode 100644
index 000000000..6de228aa5
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-3.xml
@@ -0,0 +1,150 @@
+
+
+ 4.0.0
+
+ br.com.ingenieux
+ pom-operator
+ 0.0.1-SNAPSHOT
+
+
+ UTF-8
+ 1.8
+ 1.8
+ 1.5.31
+
+
+
+
+ org.dom4j
+ dom4j
+ 2.1.3
+
+
+ jaxen
+ jaxen
+ 1.2.0
+
+
+ xerces
+ xercesImpl
+ 2.12.1
+
+
+ io.github.java-diff-utils
+ java-diff-utils
+ 4.9
+
+
+ junit
+ junit
+ 4.11
+ test
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jdk8
+ ${kotlin.version}
+
+
+ org.jetbrains.kotlin
+ kotlin-test
+ ${kotlin.version}
+ test
+
+
+
+
+
+
+
+
+ maven-clean-plugin
+ 3.1.0
+
+
+
+ maven-resources-plugin
+ 3.0.2
+
+
+ maven-compiler-plugin
+ 3.8.0
+
+
+ maven-surefire-plugin
+ 2.22.1
+
+
+ maven-jar-plugin
+ 3.0.2
+
+
+ maven-install-plugin
+ 2.5.2
+
+
+ maven-deploy-plugin
+ 2.8.2
+
+
+
+ maven-site-plugin
+ 3.7.1
+
+
+ maven-project-info-reports-plugin
+ 3.0.0
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+ ${kotlin.version}
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ test-compile
+ test-compile
+
+ test-compile
+
+
+
+
+ 1.8
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ testCompile
+ test-compile
+
+ testCompile
+
+
+
+
+
+
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-4-result.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-4-result.xml
new file mode 100644
index 000000000..324a9a7ec
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-4-result.xml
@@ -0,0 +1,242 @@
+
+ 4.0.0
+ webgoat-server
+ jar
+
+ org.owasp.webgoat
+ webgoat-parent
+ 8.2.3-SNAPSHOT
+
+
+
+ org.owasp.webgoat.StartWebGoat
+
+
+
+
+ org.owasp.webgoat
+ webgoat-container
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ challenge
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ bypass-restrictions
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ client-side-filtering
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ crypto
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ cross-site-scripting
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ html-tampering
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ http-basics
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ http-proxies
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ cia
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ chrome-dev-tools
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ idor
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ csrf
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ insecure-login
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ insecure-deserialization
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ jwt
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ path-traversal
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ sql-injection
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ vulnerable-components
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ xxe
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ auth-bypass
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ webgoat-introduction
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ webwolf-introduction
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ missing-function-ac
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ password-reset
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ ssrf
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ secure-passwords
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ spoof-cookie
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ hijack-session
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ webgoat-lesson-template
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ logging
+ ${project.version}
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ true
+
+
+ org.postgresql
+ postgresql
+
+
+ org.apache.activemq
+ activemq-amqp
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ true
+
+
+
+ org.thymeleaf.extra
+ thymeleaf-extras-springsecurity5++
+
+
+ org.asciidoctor
+ asciidoctorj
+
+
+ org.jruby
+ jruby-complete
+
+
+
+
+
+ true
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+ test-compile
+
+ jar
+
+
+ internal
+
+
+
+
+
+
+
+
+
+ org.apache.activemq
+ activemq-amqp
+ 5.16.2
+
+
+
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-4.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-4.xml
new file mode 100644
index 000000000..2b6f8f8a4
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-4.xml
@@ -0,0 +1,230 @@
+
+ 4.0.0
+ webgoat-server
+ jar
+
+ org.owasp.webgoat
+ webgoat-parent
+ 8.2.3-SNAPSHOT
+
+
+
+ org.owasp.webgoat.StartWebGoat
+
+
+
+
+ org.owasp.webgoat
+ webgoat-container
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ challenge
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ bypass-restrictions
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ client-side-filtering
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ crypto
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ cross-site-scripting
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ html-tampering
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ http-basics
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ http-proxies
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ cia
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ chrome-dev-tools
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ idor
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ csrf
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ insecure-login
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ insecure-deserialization
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ jwt
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ path-traversal
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ sql-injection
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ vulnerable-components
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ xxe
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ auth-bypass
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ webgoat-introduction
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ webwolf-introduction
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ missing-function-ac
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ password-reset
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ ssrf
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ secure-passwords
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ spoof-cookie
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ hijack-session
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ webgoat-lesson-template
+ ${project.version}
+
+
+ org.owasp.webgoat.lesson
+ logging
+ ${project.version}
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ true
+
+
+ org.postgresql
+ postgresql
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ true
+
+
+
+ org.thymeleaf.extra
+ thymeleaf-extras-springsecurity5++
+
+
+ org.asciidoctor
+ asciidoctorj
+
+
+ org.jruby
+ jruby-complete
+
+
+
+
+
+ true
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+ test-compile
+
+ jar
+
+
+ internal
+
+
+
+
+
+
+
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-5-result.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-5-result.xml
new file mode 100644
index 000000000..0f3bdca62
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-5-result.xml
@@ -0,0 +1,820 @@
+
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.7.1
+
+ org.owasp.webgoat
+ webgoat
+ 2023.5-SNAPSHOT
+ jar
+
+ WebGoat
+ WebGoat, a deliberately insecure Web Application
+ https://github.com/WebGoat/WebGoat
+ 2006
+
+ OWASP
+ https://github.com/WebGoat/WebGoat/
+
+
+
+ GNU General Public License, version 2
+ https://www.gnu.org/licenses/gpl-2.0.txt
+
+
+
+
+ mayhew64
+ Bruce Mayhew
+ webgoat@owasp.org
+ OWASP
+ https://github.com/WebGoat/WebGoat
+
+
+ nbaars
+ Nanne Baars
+ nanne.baars@owasp.org
+ https://github.com/nbaars
+ Europe/Amsterdam
+
+
+ misfir3
+ Jason White
+ jason.white@owasp.org
+
+
+ zubcevic
+ René Zubcevic
+ rene.zubcevic@owasp.org
+
+
+ aolle
+ Àngel Ollé Blázquez
+ angel@olleb.com
+
+
+ jwayman
+ Jeff Wayman
+
+
+
+ dcowden
+ Dave Cowden
+
+
+
+ lawson89
+ Richard Lawson
+
+
+
+ dougmorato
+ Doug Morato
+ doug.morato@owasp.org
+ OWASP
+ https://github.com/dougmorato
+ America/New_York
+
+ https://avatars2.githubusercontent.com/u/9654?v=3&s=150
+
+
+
+
+
+
+ OWASP WebGoat Mailing List
+ https://lists.owasp.org/mailman/listinfo/owasp-webgoat
+ Owasp-webgoat-request@lists.owasp.org
+ owasp-webgoat@lists.owasp.org
+ http://lists.owasp.org/pipermail/owasp-webgoat/
+
+
+
+
+ scm:git:git@github.com:WebGoat/WebGoat.git
+ scm:git:git@github.com:WebGoat/WebGoat.git
+ HEAD
+ https://github.com/WebGoat/WebGoat
+
+
+
+ Github Issues
+ https://github.com/WebGoat/WebGoat/issues
+
+
+
+
+ 2.5.3
+ 3.3.7
+ 2.2
+
+ 3.1.2
+ 3.2.1
+ 2.11.0
+ 3.12.0
+ 1.10.0
+ 30.1-jre
+ 0.8.8
+ 17
+ 0.9.1
+ 0.9.3
+ 3.5.1
+ 1.15.4
+ 3.8.0
+ 2.22.0
+ 3.1.2
+ 3.1.1
+ 3.1.0
+ 3.0.0-M9
+ 17
+ 17
+ 3.15.0
+
+ UTF-8
+ UTF-8
+ 3.0.15.RELEASE
+ 5.3.2
+ 8080
+ 9090
+ 2.27.2
+ 1.2
+ 1.4.5
+
+ 1.5.2
+ 1.0.2
+
+
+
+
+
+
+ org.ow2.asm
+ asm
+ 9.1
+
+
+
+ org.apache.commons
+ commons-exec
+ 1.3
+
+
+ org.asciidoctor
+ asciidoctorj
+ ${asciidoctorj.version}
+
+
+
+ org.jsoup
+ jsoup
+ ${jsoup.version}
+
+
+ com.nulab-inc
+ zxcvbn
+ ${zxcvbn.version}
+
+
+ com.thoughtworks.xstream
+ xstream
+ ${xstream.version}
+
+
+ cglib
+ cglib-nodep
+ ${cglib.version}
+
+
+ xml-resolver
+ xml-resolver
+ ${xml-resolver.version}
+
+
+ io.jsonwebtoken
+ jjwt
+ ${jjwt.version}
+
+
+ com.google.guava
+ guava
+ ${guava.version}
+
+
+ commons-io
+ commons-io
+ ${commons-io.version}
+
+
+ org.apache.commons
+ commons-text
+ ${commons-text.version}
+
+
+ org.bitbucket.b_c
+ jose4j
+ ${jose4j.version}
+
+
+ org.webjars
+ bootstrap
+ ${bootstrap.version}
+
+
+ org.webjars
+ jquery
+ ${jquery.version}
+
+
+ com.github.tomakehurst
+ wiremock
+ ${wiremock.version}
+
+
+ io.github.bonigarcia
+ webdrivermanager
+ ${webdriver.version}
+
+
+ org.apache.commons
+ commons-compress
+ 1.22
+
+
+ org.jruby
+ jruby
+ 9.3.6.0
+
+
+ io.github.pixee
+ java-security-toolkit
+ ${versions.java-security-toolkit}
+
+
+
+
+
+
+ org.apache.commons
+ commons-exec
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.projectlombok
+ lombok
+ provided
+ true
+
+
+ javax.xml.bind
+ jaxb-api
+
+
+ org.springframework.boot
+ spring-boot-starter-undertow
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-tomcat
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.flywaydb
+ flyway-core
+
+
+ org.asciidoctor
+ asciidoctorj
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.boot
+ spring-boot-starter-thymeleaf
+
+
+ org.thymeleaf.extras
+ thymeleaf-extras-springsecurity5
+
+
+ org.hsqldb
+ hsqldb
+
+
+ org.jsoup
+ jsoup
+
+
+ com.nulab-inc
+ zxcvbn
+
+
+ com.thoughtworks.xstream
+ xstream
+
+
+ cglib
+ cglib-nodep
+
+
+ xml-resolver
+ xml-resolver
+
+
+ io.jsonwebtoken
+ jjwt
+
+
+ com.google.guava
+ guava
+
+
+ commons-io
+ commons-io
+
+
+ org.apache.commons
+ commons-lang3
+
+
+ org.apache.commons
+ commons-text
+
+
+ org.bitbucket.b_c
+ jose4j
+
+
+ org.webjars
+ bootstrap
+
+
+ org.webjars
+ jquery
+
+
+ org.glassfish.jaxb
+ jaxb-runtime
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.security
+ spring-security-test
+ test
+
+
+ com.github.tomakehurst
+ wiremock
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+ io.github.pixee
+ java-security-toolkit
+
+
+
+
+
+
+ false
+
+ central
+ https://repo.maven.apache.org/maven2
+
+
+
+
+
+ false
+
+ central
+ https://repo.maven.apache.org/maven2
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ true
+ true
+ org.owasp.webgoat.server.StartWebGoat
+
+
+
+ org.asciidoctor
+ asciidoctorj
+
+
+
+
+
+
+ repackage
+
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+
+
+ add-integration-test-source-as-test-sources
+
+ add-test-source
+
+ generate-test-sources
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+ ${basedir}/src/test/resources/logback-test.xml
+
+ -Xmx512m -Dwebgoatport=${webgoat.port} -Dwebwolfport=${webwolf.port}
+ org/owasp/webgoat/*Test
+
+
+
+ integration-test
+
+ integration-test
+
+
+
+ verify
+
+ verify
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${maven-surefire-plugin.version}
+
+ --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED
+ --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED
+ --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED
+ --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED
+
+ **/*IntegrationTest.java
+ src/it/java
+ org/owasp/webgoat/*Test
+
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ ${checkstyle.version}
+
+ UTF-8
+ true
+ true
+ config/checkstyle/checkstyle.xml
+ config/checkstyle/suppressions.xml
+ checkstyle.suppressions.file
+
+
+
+ com.diffplug.spotless
+ spotless-maven-plugin
+ 2.33.0
+
+
+
+
+ .gitignore
+
+
+
+
+ true
+ 4
+
+
+
+
+
+ **/*.md
+
+
+
+
+
+ src/main/java/**/*.java
+ src/test/java/**/*.java
+ src/it/java/**/*.java
+
+
+
+
+ true
+
+
+
+
+ UTF-8
+ ${line.separator}
+ true
+ false
+ true
+ 2
+ false
+ false
+ recommended_2008_06
+ true
+ true
+ true
+
+
+
+
+
+
+ check
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
+ 3.2.1
+
+
+ restrict-log4j-versions
+
+ enforce
+
+ validate
+
+
+
+
+ org.apache.logging.log4j:log4j-core
+
+
+
+ true
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ 17
+
+
+
+
+
+
+
+ local-server
+
+
+ start-server
+
+ true
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+
+
+ reserve-container-port
+
+ reserve-network-port
+
+ process-resources
+
+
+ webgoat.port
+ webwolf.port
+ jmxPort
+
+
+
+
+
+
+ com.bazaarvoice.maven.plugins
+ process-exec-maven-plugin
+ 0.9
+
+
+ start-jar
+
+ start
+
+ pre-integration-test
+
+ ${project.build.directory}
+
+ java
+ -jar
+ -Dlogging.pattern.console=
+ -Dwebgoat.server.directory=${java.io.tmpdir}/webgoat_${webgoat.port}
+ -Dwebgoat.user.directory=${java.io.tmpdir}/webgoat_${webgoat.port}
+ -Dspring.main.banner-mode=off
+ -Dwebgoat.port=${webgoat.port}
+ -Dwebwolf.port=${webwolf.port}
+ --add-opens
+ java.base/java.lang=ALL-UNNAMED
+ --add-opens
+ java.base/java.util=ALL-UNNAMED
+ --add-opens
+ java.base/java.lang.reflect=ALL-UNNAMED
+ --add-opens
+ java.base/java.text=ALL-UNNAMED
+ --add-opens
+ java.desktop/java.beans=ALL-UNNAMED
+ --add-opens
+ java.desktop/java.awt.font=ALL-UNNAMED
+ --add-opens
+ java.base/sun.nio.ch=ALL-UNNAMED
+ --add-opens
+ java.base/java.io=ALL-UNNAMED
+ --add-opens
+ java.base/java.util=ALL-UNNAMED
+ ${project.build.directory}/webgoat-${project.version}.jar
+
+ false
+ http://localhost:${webgoat.port}/WebGoat/actuator/health
+
+
+
+ stop-jar-process
+
+ stop-all
+
+ post-integration-test
+
+
+
+
+
+
+
+ owasp
+
+ false
+
+
+
+
+ org.owasp
+ dependency-check-maven
+ 6.5.1
+
+ 7
+ false
+ false
+
+
+ ${maven.multiModuleProjectDirectory}/config/dependency-check/project-suppression.xml
+
+
+
+
+
+ check
+
+
+
+
+
+
+
+
+
+ coverage
+
+ false
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${maven-surefire-plugin.version}
+
+ --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED
+ --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED
+ --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED
+ --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED
+ ${surefire.jacoco.args}
+
+ **/*IntegrationTest.java
+ src/it/java
+ org/owasp/webgoat/*Test
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ ${jacoco.version}
+
+
+ before-unit-test
+
+ prepare-agent
+
+
+ ${project.build.directory}/jacoco/jacoco-ut.exec
+ surefire.jacoco.args
+
+
+
+ check
+
+ check
+
+
+
+
+ BUNDLE
+
+
+ CLASS
+ COVEREDCOUNT
+ 0.6
+
+
+
+
+ ${project.build.directory}/jacoco/jacoco-ut.exec
+
+
+
+ after-unit-test
+
+ report
+
+ test
+
+ ${project.build.directory}/jacoco/jacoco-ut.exec
+ ${project.reporting.outputDirectory}/jacoco-unit-test-coverage-report
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-5.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-5.xml
new file mode 100644
index 000000000..24b8db115
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-5.xml
@@ -0,0 +1,810 @@
+
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.7.1
+
+ org.owasp.webgoat
+ webgoat
+ 2023.5-SNAPSHOT
+ jar
+
+ WebGoat
+ WebGoat, a deliberately insecure Web Application
+ https://github.com/WebGoat/WebGoat
+ 2006
+
+ OWASP
+ https://github.com/WebGoat/WebGoat/
+
+
+
+ GNU General Public License, version 2
+ https://www.gnu.org/licenses/gpl-2.0.txt
+
+
+
+
+ mayhew64
+ Bruce Mayhew
+ webgoat@owasp.org
+ OWASP
+ https://github.com/WebGoat/WebGoat
+
+
+ nbaars
+ Nanne Baars
+ nanne.baars@owasp.org
+ https://github.com/nbaars
+ Europe/Amsterdam
+
+
+ misfir3
+ Jason White
+ jason.white@owasp.org
+
+
+ zubcevic
+ René Zubcevic
+ rene.zubcevic@owasp.org
+
+
+ aolle
+ Àngel Ollé Blázquez
+ angel@olleb.com
+
+
+ jwayman
+ Jeff Wayman
+
+
+
+ dcowden
+ Dave Cowden
+
+
+
+ lawson89
+ Richard Lawson
+
+
+
+ dougmorato
+ Doug Morato
+ doug.morato@owasp.org
+ OWASP
+ https://github.com/dougmorato
+ America/New_York
+
+ https://avatars2.githubusercontent.com/u/9654?v=3&s=150
+
+
+
+
+
+
+ OWASP WebGoat Mailing List
+ https://lists.owasp.org/mailman/listinfo/owasp-webgoat
+ Owasp-webgoat-request@lists.owasp.org
+ owasp-webgoat@lists.owasp.org
+ http://lists.owasp.org/pipermail/owasp-webgoat/
+
+
+
+
+ scm:git:git@github.com:WebGoat/WebGoat.git
+ scm:git:git@github.com:WebGoat/WebGoat.git
+ HEAD
+ https://github.com/WebGoat/WebGoat
+
+
+
+ Github Issues
+ https://github.com/WebGoat/WebGoat/issues
+
+
+
+
+ 2.5.3
+ 3.3.7
+ 2.2
+
+ 3.1.2
+ 3.2.1
+ 2.11.0
+ 3.12.0
+ 1.10.0
+ 30.1-jre
+ 0.8.8
+ 17
+ 0.9.1
+ 0.9.3
+ 3.5.1
+ 1.15.4
+ 3.8.0
+ 2.22.0
+ 3.1.2
+ 3.1.1
+ 3.1.0
+ 3.0.0-M9
+ 17
+ 17
+ 3.15.0
+
+ UTF-8
+ UTF-8
+ 3.0.15.RELEASE
+ 5.3.2
+ 8080
+ 9090
+ 2.27.2
+ 1.2
+ 1.4.5
+
+ 1.5.2
+
+
+
+
+
+
+ org.ow2.asm
+ asm
+ 9.1
+
+
+
+ org.apache.commons
+ commons-exec
+ 1.3
+
+
+ org.asciidoctor
+ asciidoctorj
+ ${asciidoctorj.version}
+
+
+
+ org.jsoup
+ jsoup
+ ${jsoup.version}
+
+
+ com.nulab-inc
+ zxcvbn
+ ${zxcvbn.version}
+
+
+ com.thoughtworks.xstream
+ xstream
+ ${xstream.version}
+
+
+ cglib
+ cglib-nodep
+ ${cglib.version}
+
+
+ xml-resolver
+ xml-resolver
+ ${xml-resolver.version}
+
+
+ io.jsonwebtoken
+ jjwt
+ ${jjwt.version}
+
+
+ com.google.guava
+ guava
+ ${guava.version}
+
+
+ commons-io
+ commons-io
+ ${commons-io.version}
+
+
+ org.apache.commons
+ commons-text
+ ${commons-text.version}
+
+
+ org.bitbucket.b_c
+ jose4j
+ ${jose4j.version}
+
+
+ org.webjars
+ bootstrap
+ ${bootstrap.version}
+
+
+ org.webjars
+ jquery
+ ${jquery.version}
+
+
+ com.github.tomakehurst
+ wiremock
+ ${wiremock.version}
+
+
+ io.github.bonigarcia
+ webdrivermanager
+ ${webdriver.version}
+
+
+ org.apache.commons
+ commons-compress
+ 1.22
+
+
+ org.jruby
+ jruby
+ 9.3.6.0
+
+
+
+
+
+
+ org.apache.commons
+ commons-exec
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.projectlombok
+ lombok
+ provided
+ true
+
+
+ javax.xml.bind
+ jaxb-api
+
+
+ org.springframework.boot
+ spring-boot-starter-undertow
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-tomcat
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.flywaydb
+ flyway-core
+
+
+ org.asciidoctor
+ asciidoctorj
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.boot
+ spring-boot-starter-thymeleaf
+
+
+ org.thymeleaf.extras
+ thymeleaf-extras-springsecurity5
+
+
+ org.hsqldb
+ hsqldb
+
+
+ org.jsoup
+ jsoup
+
+
+ com.nulab-inc
+ zxcvbn
+
+
+ com.thoughtworks.xstream
+ xstream
+
+
+ cglib
+ cglib-nodep
+
+
+ xml-resolver
+ xml-resolver
+
+
+ io.jsonwebtoken
+ jjwt
+
+
+ com.google.guava
+ guava
+
+
+ commons-io
+ commons-io
+
+
+ org.apache.commons
+ commons-lang3
+
+
+ org.apache.commons
+ commons-text
+
+
+ org.bitbucket.b_c
+ jose4j
+
+
+ org.webjars
+ bootstrap
+
+
+ org.webjars
+ jquery
+
+
+ org.glassfish.jaxb
+ jaxb-runtime
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.security
+ spring-security-test
+ test
+
+
+ com.github.tomakehurst
+ wiremock
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+
+
+
+
+ false
+
+ central
+ https://repo.maven.apache.org/maven2
+
+
+
+
+
+ false
+
+ central
+ https://repo.maven.apache.org/maven2
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ true
+ true
+ org.owasp.webgoat.server.StartWebGoat
+
+
+
+ org.asciidoctor
+ asciidoctorj
+
+
+
+
+
+
+ repackage
+
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+
+
+ add-integration-test-source-as-test-sources
+
+ add-test-source
+
+ generate-test-sources
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+ ${basedir}/src/test/resources/logback-test.xml
+
+ -Xmx512m -Dwebgoatport=${webgoat.port} -Dwebwolfport=${webwolf.port}
+ org/owasp/webgoat/*Test
+
+
+
+ integration-test
+
+ integration-test
+
+
+
+ verify
+
+ verify
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${maven-surefire-plugin.version}
+
+ --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED
+ --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED
+ --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED
+ --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED
+
+ **/*IntegrationTest.java
+ src/it/java
+ org/owasp/webgoat/*Test
+
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ ${checkstyle.version}
+
+ UTF-8
+ true
+ true
+ config/checkstyle/checkstyle.xml
+ config/checkstyle/suppressions.xml
+ checkstyle.suppressions.file
+
+
+
+ com.diffplug.spotless
+ spotless-maven-plugin
+ 2.33.0
+
+
+
+
+ .gitignore
+
+
+
+
+ true
+ 4
+
+
+
+
+
+ **/*.md
+
+
+
+
+
+ src/main/java/**/*.java
+ src/test/java/**/*.java
+ src/it/java/**/*.java
+
+
+
+
+ true
+
+
+
+
+ UTF-8
+ ${line.separator}
+ true
+ false
+ true
+ 2
+ false
+ false
+ recommended_2008_06
+ true
+ true
+ true
+
+
+
+
+
+
+ check
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
+ 3.2.1
+
+
+ restrict-log4j-versions
+
+ enforce
+
+ validate
+
+
+
+
+ org.apache.logging.log4j:log4j-core
+
+
+
+ true
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ 17
+
+
+
+
+
+
+
+ local-server
+
+
+ start-server
+
+ true
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+
+
+ reserve-container-port
+
+ reserve-network-port
+
+ process-resources
+
+
+ webgoat.port
+ webwolf.port
+ jmxPort
+
+
+
+
+
+
+ com.bazaarvoice.maven.plugins
+ process-exec-maven-plugin
+ 0.9
+
+
+ start-jar
+
+ start
+
+ pre-integration-test
+
+ ${project.build.directory}
+
+ java
+ -jar
+ -Dlogging.pattern.console=
+ -Dwebgoat.server.directory=${java.io.tmpdir}/webgoat_${webgoat.port}
+ -Dwebgoat.user.directory=${java.io.tmpdir}/webgoat_${webgoat.port}
+ -Dspring.main.banner-mode=off
+ -Dwebgoat.port=${webgoat.port}
+ -Dwebwolf.port=${webwolf.port}
+ --add-opens
+ java.base/java.lang=ALL-UNNAMED
+ --add-opens
+ java.base/java.util=ALL-UNNAMED
+ --add-opens
+ java.base/java.lang.reflect=ALL-UNNAMED
+ --add-opens
+ java.base/java.text=ALL-UNNAMED
+ --add-opens
+ java.desktop/java.beans=ALL-UNNAMED
+ --add-opens
+ java.desktop/java.awt.font=ALL-UNNAMED
+ --add-opens
+ java.base/sun.nio.ch=ALL-UNNAMED
+ --add-opens
+ java.base/java.io=ALL-UNNAMED
+ --add-opens
+ java.base/java.util=ALL-UNNAMED
+ ${project.build.directory}/webgoat-${project.version}.jar
+
+ false
+ http://localhost:${webgoat.port}/WebGoat/actuator/health
+
+
+
+ stop-jar-process
+
+ stop-all
+
+ post-integration-test
+
+
+
+
+
+
+
+ owasp
+
+ false
+
+
+
+
+ org.owasp
+ dependency-check-maven
+ 6.5.1
+
+ 7
+ false
+ false
+
+
+ ${maven.multiModuleProjectDirectory}/config/dependency-check/project-suppression.xml
+
+
+
+
+
+ check
+
+
+
+
+
+
+
+
+
+ coverage
+
+ false
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${maven-surefire-plugin.version}
+
+ --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED
+ --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED
+ --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED
+ --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED
+ ${surefire.jacoco.args}
+
+ **/*IntegrationTest.java
+ src/it/java
+ org/owasp/webgoat/*Test
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ ${jacoco.version}
+
+
+ before-unit-test
+
+ prepare-agent
+
+
+ ${project.build.directory}/jacoco/jacoco-ut.exec
+ surefire.jacoco.args
+
+
+
+ check
+
+ check
+
+
+
+
+ BUNDLE
+
+
+ CLASS
+ COVEREDCOUNT
+ 0.6
+
+
+
+
+ ${project.build.directory}/jacoco/jacoco-ut.exec
+
+
+
+ after-unit-test
+
+ report
+
+ test
+
+ ${project.build.directory}/jacoco/jacoco-ut.exec
+ ${project.reporting.outputDirectory}/jacoco-unit-test-coverage-report
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-6-result.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-6-result.xml
new file mode 100644
index 000000000..8be353890
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-6-result.xml
@@ -0,0 +1,824 @@
+
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.7.1
+
+ org.owasp.webgoat
+ webgoat
+ 2023.5-SNAPSHOT
+ jar
+
+ WebGoat
+ WebGoat, a deliberately insecure Web Application
+ https://github.com/WebGoat/WebGoat
+ 2006
+
+ OWASP
+ https://github.com/WebGoat/WebGoat/
+
+
+
+ GNU General Public License, version 2
+ https://www.gnu.org/licenses/gpl-2.0.txt
+
+
+
+
+ mayhew64
+ Bruce Mayhew
+ webgoat@owasp.org
+ OWASP
+ https://github.com/WebGoat/WebGoat
+
+
+ nbaars
+ Nanne Baars
+ nanne.baars@owasp.org
+ https://github.com/nbaars
+ Europe/Amsterdam
+
+
+ misfir3
+ Jason White
+ jason.white@owasp.org
+
+
+ zubcevic
+ René Zubcevic
+ rene.zubcevic@owasp.org
+
+
+ aolle
+ Àngel Ollé Blázquez
+ angel@olleb.com
+
+
+ jwayman
+ Jeff Wayman
+
+
+
+
+
+ dcowden
+ Dave Cowden
+
+
+
+ lawson89
+ Richard Lawson
+
+
+
+ dougmorato
+ Doug Morato
+ doug.morato@owasp.org
+ OWASP
+ https://github.com/dougmorato
+ America/New_York
+
+ https://avatars2.githubusercontent.com/u/9654?v=3&s=150
+
+
+
+
+
+
+ OWASP WebGoat Mailing List
+ https://lists.owasp.org/mailman/listinfo/owasp-webgoat
+ Owasp-webgoat-request@lists.owasp.org
+ owasp-webgoat@lists.owasp.org
+ http://lists.owasp.org/pipermail/owasp-webgoat/
+
+
+
+
+ scm:git:git@github.com:WebGoat/WebGoat.git
+ scm:git:git@github.com:WebGoat/WebGoat.git
+ HEAD
+ https://github.com/WebGoat/WebGoat
+
+
+
+ Github Issues
+ https://github.com/WebGoat/WebGoat/issues
+
+
+
+
+ 2.5.3
+ 3.3.7
+ 2.2
+
+ 3.1.2
+ 3.2.1
+ 2.11.0
+ 3.12.0
+ 1.10.0
+ 30.1-jre
+ 0.8.8
+ 17
+ 0.9.1
+ 0.9.3
+ 3.5.1
+ 1.15.4
+ 3.8.0
+ 2.22.0
+ 3.1.2
+ 3.1.1
+ 3.1.0
+ 3.0.0-M9
+ 17
+ 17
+ 3.15.0
+
+ UTF-8
+ UTF-8
+ 3.0.15.RELEASE
+ 5.3.2
+ 8080
+ 9090
+ 2.27.2
+ 1.2
+ 1.4.5
+
+ 1.5.2
+ 1.0.2
+
+
+
+
+
+
+ org.ow2.asm
+ asm
+ 9.1
+
+
+
+ org.apache.commons
+ commons-exec
+ 1.3
+
+
+ org.asciidoctor
+ asciidoctorj
+ ${asciidoctorj.version}
+
+
+
+ org.jsoup
+ jsoup
+ ${jsoup.version}
+
+
+ com.nulab-inc
+ zxcvbn
+ ${zxcvbn.version}
+
+
+ com.thoughtworks.xstream
+ xstream
+ ${xstream.version}
+
+
+ cglib
+ cglib-nodep
+ ${cglib.version}
+
+
+ xml-resolver
+ xml-resolver
+ ${xml-resolver.version}
+
+
+ io.jsonwebtoken
+ jjwt
+ ${jjwt.version}
+
+
+ com.google.guava
+ guava
+ ${guava.version}
+
+
+ commons-io
+ commons-io
+ ${commons-io.version}
+
+
+ org.apache.commons
+ commons-text
+ ${commons-text.version}
+
+
+ org.bitbucket.b_c
+ jose4j
+ ${jose4j.version}
+
+
+ org.webjars
+ bootstrap
+ ${bootstrap.version}
+
+
+ org.webjars
+ jquery
+ ${jquery.version}
+
+
+ com.github.tomakehurst
+ wiremock
+ ${wiremock.version}
+
+
+ io.github.bonigarcia
+ webdrivermanager
+ ${webdriver.version}
+
+
+ org.apache.commons
+ commons-compress
+ 1.22
+
+
+ org.jruby
+ jruby
+ 9.3.6.0
+
+
+ io.github.pixee
+ java-security-toolkit
+ ${versions.java-security-toolkit}
+
+
+
+
+
+
+ org.apache.commons
+ commons-exec
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.projectlombok
+ lombok
+ provided
+ true
+
+
+ javax.xml.bind
+ jaxb-api
+
+
+ org.springframework.boot
+ spring-boot-starter-undertow
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-tomcat
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.flywaydb
+ flyway-core
+
+
+ org.asciidoctor
+ asciidoctorj
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.boot
+ spring-boot-starter-thymeleaf
+
+
+ org.thymeleaf.extras
+ thymeleaf-extras-springsecurity5
+
+
+ org.hsqldb
+ hsqldb
+
+
+ org.jsoup
+ jsoup
+
+
+ com.nulab-inc
+ zxcvbn
+
+
+ com.thoughtworks.xstream
+ xstream
+
+
+ cglib
+ cglib-nodep
+
+
+ xml-resolver
+ xml-resolver
+
+
+ io.jsonwebtoken
+ jjwt
+
+
+ com.google.guava
+ guava
+
+
+ commons-io
+ commons-io
+
+
+ org.apache.commons
+ commons-lang3
+
+
+ org.apache.commons
+ commons-text
+
+
+ org.bitbucket.b_c
+ jose4j
+
+
+ org.webjars
+ bootstrap
+
+
+ org.webjars
+ jquery
+
+
+ org.glassfish.jaxb
+ jaxb-runtime
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.security
+ spring-security-test
+ test
+
+
+ com.github.tomakehurst
+ wiremock
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+ io.github.pixee
+ java-security-toolkit
+
+
+
+
+
+
+ false
+
+ central
+ https://repo.maven.apache.org/maven2
+
+
+
+
+
+ false
+
+ central
+ https://repo.maven.apache.org/maven2
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ true
+ true
+ org.owasp.webgoat.server.StartWebGoat
+
+
+
+ org.asciidoctor
+ asciidoctorj
+
+
+
+
+
+
+ repackage
+
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+
+
+ add-integration-test-source-as-test-sources
+
+ add-test-source
+
+ generate-test-sources
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+ ${basedir}/src/test/resources/logback-test.xml
+
+ -Xmx512m -Dwebgoatport=${webgoat.port} -Dwebwolfport=${webwolf.port}
+ org/owasp/webgoat/*Test
+
+
+
+ integration-test
+
+ integration-test
+
+
+
+ verify
+
+ verify
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${maven-surefire-plugin.version}
+
+ --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED
+ --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED
+ --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED
+ --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED
+
+ **/*IntegrationTest.java
+ src/it/java
+ org/owasp/webgoat/*Test
+
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ ${checkstyle.version}
+
+ UTF-8
+ true
+ true
+ config/checkstyle/checkstyle.xml
+ config/checkstyle/suppressions.xml
+ checkstyle.suppressions.file
+
+
+
+ com.diffplug.spotless
+ spotless-maven-plugin
+ 2.33.0
+
+
+
+
+ .gitignore
+
+
+
+
+ true
+ 4
+
+
+
+
+
+ **/*.md
+
+
+
+
+
+ src/main/java/**/*.java
+ src/test/java/**/*.java
+ src/it/java/**/*.java
+
+
+
+
+ true
+
+
+
+
+ UTF-8
+ ${line.separator}
+ true
+ false
+ true
+ 2
+ false
+ false
+ recommended_2008_06
+ true
+ true
+ true
+
+
+
+
+
+
+ check
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
+ 3.2.1
+
+
+ restrict-log4j-versions
+
+ enforce
+
+ validate
+
+
+
+
+ org.apache.logging.log4j:log4j-core
+
+
+
+ true
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ 17
+
+
+
+
+
+
+
+ local-server
+
+
+ start-server
+
+ true
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+
+
+ reserve-container-port
+
+ reserve-network-port
+
+ process-resources
+
+
+ webgoat.port
+ webwolf.port
+ jmxPort
+
+
+
+
+
+
+ com.bazaarvoice.maven.plugins
+ process-exec-maven-plugin
+ 0.9
+
+
+ start-jar
+
+ start
+
+ pre-integration-test
+
+ ${project.build.directory}
+
+ java
+ -jar
+ -Dlogging.pattern.console=
+ -Dwebgoat.server.directory=${java.io.tmpdir}/webgoat_${webgoat.port}
+ -Dwebgoat.user.directory=${java.io.tmpdir}/webgoat_${webgoat.port}
+ -Dspring.main.banner-mode=off
+ -Dwebgoat.port=${webgoat.port}
+ -Dwebwolf.port=${webwolf.port}
+ --add-opens
+ java.base/java.lang=ALL-UNNAMED
+ --add-opens
+ java.base/java.util=ALL-UNNAMED
+ --add-opens
+ java.base/java.lang.reflect=ALL-UNNAMED
+ --add-opens
+ java.base/java.text=ALL-UNNAMED
+ --add-opens
+ java.desktop/java.beans=ALL-UNNAMED
+ --add-opens
+ java.desktop/java.awt.font=ALL-UNNAMED
+ --add-opens
+ java.base/sun.nio.ch=ALL-UNNAMED
+ --add-opens
+ java.base/java.io=ALL-UNNAMED
+ --add-opens
+ java.base/java.util=ALL-UNNAMED
+ ${project.build.directory}/webgoat-${project.version}.jar
+
+ false
+ http://localhost:${webgoat.port}/WebGoat/actuator/health
+
+
+
+ stop-jar-process
+
+ stop-all
+
+ post-integration-test
+
+
+
+
+
+
+
+ owasp
+
+ false
+
+
+
+
+ org.owasp
+ dependency-check-maven
+ 6.5.1
+
+ 7
+ false
+ false
+
+
+ ${maven.multiModuleProjectDirectory}/config/dependency-check/project-suppression.xml
+
+
+
+
+
+ check
+
+
+
+
+
+
+
+
+
+ coverage
+
+ false
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${maven-surefire-plugin.version}
+
+ --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED
+ --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED
+ --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED
+ --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED
+ ${surefire.jacoco.args}
+
+ **/*IntegrationTest.java
+ src/it/java
+ org/owasp/webgoat/*Test
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ ${jacoco.version}
+
+
+ before-unit-test
+
+ prepare-agent
+
+
+ ${project.build.directory}/jacoco/jacoco-ut.exec
+ surefire.jacoco.args
+
+
+
+ check
+
+ check
+
+
+
+
+ BUNDLE
+
+
+ CLASS
+ COVEREDCOUNT
+ 0.6
+
+
+
+
+ ${project.build.directory}/jacoco/jacoco-ut.exec
+
+
+
+ after-unit-test
+
+ report
+
+ test
+
+ ${project.build.directory}/jacoco/jacoco-ut.exec
+ ${project.reporting.outputDirectory}/jacoco-unit-test-coverage-report
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-6.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-6.xml
new file mode 100644
index 000000000..de2f19e0c
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-6.xml
@@ -0,0 +1,814 @@
+
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.7.1
+
+ org.owasp.webgoat
+ webgoat
+ 2023.5-SNAPSHOT
+ jar
+
+ WebGoat
+ WebGoat, a deliberately insecure Web Application
+ https://github.com/WebGoat/WebGoat
+ 2006
+
+ OWASP
+ https://github.com/WebGoat/WebGoat/
+
+
+
+ GNU General Public License, version 2
+ https://www.gnu.org/licenses/gpl-2.0.txt
+
+
+
+
+ mayhew64
+ Bruce Mayhew
+ webgoat@owasp.org
+ OWASP
+ https://github.com/WebGoat/WebGoat
+
+
+ nbaars
+ Nanne Baars
+ nanne.baars@owasp.org
+ https://github.com/nbaars
+ Europe/Amsterdam
+
+
+ misfir3
+ Jason White
+ jason.white@owasp.org
+
+
+ zubcevic
+ René Zubcevic
+ rene.zubcevic@owasp.org
+
+
+ aolle
+ Àngel Ollé Blázquez
+ angel@olleb.com
+
+
+ jwayman
+ Jeff Wayman
+
+
+
+
+
+ dcowden
+ Dave Cowden
+
+
+
+ lawson89
+ Richard Lawson
+
+
+
+ dougmorato
+ Doug Morato
+ doug.morato@owasp.org
+ OWASP
+ https://github.com/dougmorato
+ America/New_York
+
+ https://avatars2.githubusercontent.com/u/9654?v=3&s=150
+
+
+
+
+
+
+ OWASP WebGoat Mailing List
+ https://lists.owasp.org/mailman/listinfo/owasp-webgoat
+ Owasp-webgoat-request@lists.owasp.org
+ owasp-webgoat@lists.owasp.org
+ http://lists.owasp.org/pipermail/owasp-webgoat/
+
+
+
+
+ scm:git:git@github.com:WebGoat/WebGoat.git
+ scm:git:git@github.com:WebGoat/WebGoat.git
+ HEAD
+ https://github.com/WebGoat/WebGoat
+
+
+
+ Github Issues
+ https://github.com/WebGoat/WebGoat/issues
+
+
+
+
+ 2.5.3
+ 3.3.7
+ 2.2
+
+ 3.1.2
+ 3.2.1
+ 2.11.0
+ 3.12.0
+ 1.10.0
+ 30.1-jre
+ 0.8.8
+ 17
+ 0.9.1
+ 0.9.3
+ 3.5.1
+ 1.15.4
+ 3.8.0
+ 2.22.0
+ 3.1.2
+ 3.1.1
+ 3.1.0
+ 3.0.0-M9
+ 17
+ 17
+ 3.15.0
+
+ UTF-8
+ UTF-8
+ 3.0.15.RELEASE
+ 5.3.2
+ 8080
+ 9090
+ 2.27.2
+ 1.2
+ 1.4.5
+
+ 1.5.2
+
+
+
+
+
+
+ org.ow2.asm
+ asm
+ 9.1
+
+
+
+ org.apache.commons
+ commons-exec
+ 1.3
+
+
+ org.asciidoctor
+ asciidoctorj
+ ${asciidoctorj.version}
+
+
+
+ org.jsoup
+ jsoup
+ ${jsoup.version}
+
+
+ com.nulab-inc
+ zxcvbn
+ ${zxcvbn.version}
+
+
+ com.thoughtworks.xstream
+ xstream
+ ${xstream.version}
+
+
+ cglib
+ cglib-nodep
+ ${cglib.version}
+
+
+ xml-resolver
+ xml-resolver
+ ${xml-resolver.version}
+
+
+ io.jsonwebtoken
+ jjwt
+ ${jjwt.version}
+
+
+ com.google.guava
+ guava
+ ${guava.version}
+
+
+ commons-io
+ commons-io
+ ${commons-io.version}
+
+
+ org.apache.commons
+ commons-text
+ ${commons-text.version}
+
+
+ org.bitbucket.b_c
+ jose4j
+ ${jose4j.version}
+
+
+ org.webjars
+ bootstrap
+ ${bootstrap.version}
+
+
+ org.webjars
+ jquery
+ ${jquery.version}
+
+
+ com.github.tomakehurst
+ wiremock
+ ${wiremock.version}
+
+
+ io.github.bonigarcia
+ webdrivermanager
+ ${webdriver.version}
+
+
+ org.apache.commons
+ commons-compress
+ 1.22
+
+
+ org.jruby
+ jruby
+ 9.3.6.0
+
+
+
+
+
+
+ org.apache.commons
+ commons-exec
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.projectlombok
+ lombok
+ provided
+ true
+
+
+ javax.xml.bind
+ jaxb-api
+
+
+ org.springframework.boot
+ spring-boot-starter-undertow
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-tomcat
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.flywaydb
+ flyway-core
+
+
+ org.asciidoctor
+ asciidoctorj
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.boot
+ spring-boot-starter-thymeleaf
+
+
+ org.thymeleaf.extras
+ thymeleaf-extras-springsecurity5
+
+
+ org.hsqldb
+ hsqldb
+
+
+ org.jsoup
+ jsoup
+
+
+ com.nulab-inc
+ zxcvbn
+
+
+ com.thoughtworks.xstream
+ xstream
+
+
+ cglib
+ cglib-nodep
+
+
+ xml-resolver
+ xml-resolver
+
+
+ io.jsonwebtoken
+ jjwt
+
+
+ com.google.guava
+ guava
+
+
+ commons-io
+ commons-io
+
+
+ org.apache.commons
+ commons-lang3
+
+
+ org.apache.commons
+ commons-text
+
+
+ org.bitbucket.b_c
+ jose4j
+
+
+ org.webjars
+ bootstrap
+
+
+ org.webjars
+ jquery
+
+
+ org.glassfish.jaxb
+ jaxb-runtime
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.security
+ spring-security-test
+ test
+
+
+ com.github.tomakehurst
+ wiremock
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+
+
+
+
+ false
+
+ central
+ https://repo.maven.apache.org/maven2
+
+
+
+
+
+ false
+
+ central
+ https://repo.maven.apache.org/maven2
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ true
+ true
+ org.owasp.webgoat.server.StartWebGoat
+
+
+
+ org.asciidoctor
+ asciidoctorj
+
+
+
+
+
+
+ repackage
+
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+
+
+ add-integration-test-source-as-test-sources
+
+ add-test-source
+
+ generate-test-sources
+
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+ ${basedir}/src/test/resources/logback-test.xml
+
+ -Xmx512m -Dwebgoatport=${webgoat.port} -Dwebwolfport=${webwolf.port}
+ org/owasp/webgoat/*Test
+
+
+
+ integration-test
+
+ integration-test
+
+
+
+ verify
+
+ verify
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${maven-surefire-plugin.version}
+
+ --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED
+ --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED
+ --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED
+ --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED
+
+ **/*IntegrationTest.java
+ src/it/java
+ org/owasp/webgoat/*Test
+
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ ${checkstyle.version}
+
+ UTF-8
+ true
+ true
+ config/checkstyle/checkstyle.xml
+ config/checkstyle/suppressions.xml
+ checkstyle.suppressions.file
+
+
+
+ com.diffplug.spotless
+ spotless-maven-plugin
+ 2.33.0
+
+
+
+
+ .gitignore
+
+
+
+
+ true
+ 4
+
+
+
+
+
+ **/*.md
+
+
+
+
+
+ src/main/java/**/*.java
+ src/test/java/**/*.java
+ src/it/java/**/*.java
+
+
+
+
+ true
+
+
+
+
+ UTF-8
+ ${line.separator}
+ true
+ false
+ true
+ 2
+ false
+ false
+ recommended_2008_06
+ true
+ true
+ true
+
+
+
+
+
+
+ check
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
+ 3.2.1
+
+
+ restrict-log4j-versions
+
+ enforce
+
+ validate
+
+
+
+
+ org.apache.logging.log4j:log4j-core
+
+
+
+ true
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ 17
+
+
+
+
+
+
+
+ local-server
+
+
+ start-server
+
+ true
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+
+
+ reserve-container-port
+
+ reserve-network-port
+
+ process-resources
+
+
+ webgoat.port
+ webwolf.port
+ jmxPort
+
+
+
+
+
+
+ com.bazaarvoice.maven.plugins
+ process-exec-maven-plugin
+ 0.9
+
+
+ start-jar
+
+ start
+
+ pre-integration-test
+
+ ${project.build.directory}
+
+ java
+ -jar
+ -Dlogging.pattern.console=
+ -Dwebgoat.server.directory=${java.io.tmpdir}/webgoat_${webgoat.port}
+ -Dwebgoat.user.directory=${java.io.tmpdir}/webgoat_${webgoat.port}
+ -Dspring.main.banner-mode=off
+ -Dwebgoat.port=${webgoat.port}
+ -Dwebwolf.port=${webwolf.port}
+ --add-opens
+ java.base/java.lang=ALL-UNNAMED
+ --add-opens
+ java.base/java.util=ALL-UNNAMED
+ --add-opens
+ java.base/java.lang.reflect=ALL-UNNAMED
+ --add-opens
+ java.base/java.text=ALL-UNNAMED
+ --add-opens
+ java.desktop/java.beans=ALL-UNNAMED
+ --add-opens
+ java.desktop/java.awt.font=ALL-UNNAMED
+ --add-opens
+ java.base/sun.nio.ch=ALL-UNNAMED
+ --add-opens
+ java.base/java.io=ALL-UNNAMED
+ --add-opens
+ java.base/java.util=ALL-UNNAMED
+ ${project.build.directory}/webgoat-${project.version}.jar
+
+ false
+ http://localhost:${webgoat.port}/WebGoat/actuator/health
+
+
+
+ stop-jar-process
+
+ stop-all
+
+ post-integration-test
+
+
+
+
+
+
+
+ owasp
+
+ false
+
+
+
+
+ org.owasp
+ dependency-check-maven
+ 6.5.1
+
+ 7
+ false
+ false
+
+
+ ${maven.multiModuleProjectDirectory}/config/dependency-check/project-suppression.xml
+
+
+
+
+
+ check
+
+
+
+
+
+
+
+
+
+ coverage
+
+ false
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ ${maven-surefire-plugin.version}
+
+ --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED
+ --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED
+ --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED
+ --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED
+ ${surefire.jacoco.args}
+
+ **/*IntegrationTest.java
+ src/it/java
+ org/owasp/webgoat/*Test
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ ${jacoco.version}
+
+
+ before-unit-test
+
+ prepare-agent
+
+
+ ${project.build.directory}/jacoco/jacoco-ut.exec
+ surefire.jacoco.args
+
+
+
+ check
+
+ check
+
+
+
+
+ BUNDLE
+
+
+ CLASS
+ COVEREDCOUNT
+ 0.6
+
+
+
+
+ ${project.build.directory}/jacoco/jacoco-ut.exec
+
+
+
+ after-unit-test
+
+ report
+
+ test
+
+ ${project.build.directory}/jacoco/jacoco-ut.exec
+ ${project.reporting.outputDirectory}/jacoco-unit-test-coverage-report
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-with-property-result.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-with-property-result.xml
new file mode 100644
index 000000000..55e673dca
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-with-property-result.xml
@@ -0,0 +1,32 @@
+
+
+ 4.0.0
+
+ br.com.ingenieux
+ pom-operator
+ 0.0.1-SNAPSHOT
+
+
+ 1.0.0
+
+
+
+
+ test-profile
+
+ 1.0.1
+
+
+
+
+
+
+ org.dom4j
+ dom4j
+ ${sample.version}
+
+
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-with-tabs-result.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-with-tabs-result.xml
new file mode 100644
index 000000000..d9ce1162a
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-case-with-tabs-result.xml
@@ -0,0 +1,69 @@
+
+
+ 4.0.0
+
+ build-utils
+ org.modafocas.mojo
+ 0.0.1-SNAPSHOT
+ ../pom.xml
+
+
+ derby-maven-plugin
+ maven-plugin
+
+
+
+ org.apache.maven
+ maven-plugin-api
+ 2.0
+
+
+ junit
+ junit
+ 3.8.1
+ test
+
+
+ org.apache.derby
+ derby
+ ${derbyVersion}
+
+
+ org.apache.derby
+ derbynet
+ ${derbyVersion}
+
+
+ org.apache.derby
+ derbyclient
+ ${derbyVersion}
+
+
+ commons-io
+ commons-io
+ 1.4
+ jar
+ compile
+
+
+ org.dom4j
+ dom4j
+
+
+
+
+ 10.6.2.1
+ 1.0.0
+
+
+
+
+ org.dom4j
+ dom4j
+ ${versions.dom4j}
+
+
+
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-multiple-pom-basic-no-version-property-result.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-multiple-pom-basic-no-version-property-result.xml
new file mode 100644
index 000000000..3a98f80f1
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-multiple-pom-basic-no-version-property-result.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+
+ br.com.ingenieux
+ sample-parent
+ 1
+ ./sample-parent
+
+
+ sample-child-with-relativepath
+
+
+ org.dom4j
+ dom4j
+
+
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-multiple-pom-basic-result.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-multiple-pom-basic-result.xml
new file mode 100644
index 000000000..96bf62b22
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-multiple-pom-basic-result.xml
@@ -0,0 +1,16 @@
+
+
+ 4.0.0
+
+
+ br.com.ingenieux
+ sample-parent
+ 1
+ ./sample-parent
+
+
+ sample-child-with-relativepath
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-multiple-pom-basic-with-version-property-result.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-multiple-pom-basic-with-version-property-result.xml
new file mode 100644
index 000000000..3a98f80f1
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-multiple-pom-basic-with-version-property-result.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+
+ br.com.ingenieux
+ sample-parent
+ 1
+ ./sample-parent
+
+
+ sample-child-with-relativepath
+
+
+ org.dom4j
+ dom4j
+
+
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-multiple-pom-parent-level-3.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-multiple-pom-parent-level-3.xml
new file mode 100644
index 000000000..9548f8f86
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-multiple-pom-parent-level-3.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+
+ br.com.ingenieux
+ org-pom
+ 1
+ ./sample-parent/pom-with-parent-level-3.xml
+
+
+ sample-child-with-relativepath
+
+
+ org.dom4j
+ dom4j
+
+
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-multiple-pom-parent-level-4.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-multiple-pom-parent-level-4.xml
new file mode 100644
index 000000000..adaa98907
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-multiple-pom-parent-level-4.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+
+ br.com.ingenieux
+ org-pom
+ 1
+ ./sample-parent/pom-with-parent-level-4.xml
+
+
+ sample-child-with-relativepath
+
+
+ org.dom4j
+ dom4j
+
+
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-pom-case-three-with-lower-version-result.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-pom-case-three-with-lower-version-result.xml
new file mode 100644
index 000000000..6de228aa5
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-pom-case-three-with-lower-version-result.xml
@@ -0,0 +1,150 @@
+
+
+ 4.0.0
+
+ br.com.ingenieux
+ pom-operator
+ 0.0.1-SNAPSHOT
+
+
+ UTF-8
+ 1.8
+ 1.8
+ 1.5.31
+
+
+
+
+ org.dom4j
+ dom4j
+ 2.1.3
+
+
+ jaxen
+ jaxen
+ 1.2.0
+
+
+ xerces
+ xercesImpl
+ 2.12.1
+
+
+ io.github.java-diff-utils
+ java-diff-utils
+ 4.9
+
+
+ junit
+ junit
+ 4.11
+ test
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jdk8
+ ${kotlin.version}
+
+
+ org.jetbrains.kotlin
+ kotlin-test
+ ${kotlin.version}
+ test
+
+
+
+
+
+
+
+
+ maven-clean-plugin
+ 3.1.0
+
+
+
+ maven-resources-plugin
+ 3.0.2
+
+
+ maven-compiler-plugin
+ 3.8.0
+
+
+ maven-surefire-plugin
+ 2.22.1
+
+
+ maven-jar-plugin
+ 3.0.2
+
+
+ maven-install-plugin
+ 2.5.2
+
+
+ maven-deploy-plugin
+ 2.8.2
+
+
+
+ maven-site-plugin
+ 3.7.1
+
+
+ maven-project-info-reports-plugin
+ 3.0.0
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+ ${kotlin.version}
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ test-compile
+ test-compile
+
+ test-compile
+
+
+
+
+ 1.8
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ compile
+ compile
+
+ compile
+
+
+
+ testCompile
+ test-compile
+
+ testCompile
+
+
+
+
+
+
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-with-parent-level-1.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-with-parent-level-1.xml
new file mode 100644
index 000000000..d00aa1387
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-with-parent-level-1.xml
@@ -0,0 +1,13 @@
+
+
+ 4.0.0
+
+ br.com.ingenieux
+ super-pom
+ 1
+
+ pom
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-with-property-simple.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-with-property-simple.xml
new file mode 100644
index 000000000..d2f9f3b26
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/pom-with-property-simple.xml
@@ -0,0 +1,32 @@
+
+
+ 4.0.0
+
+ br.com.ingenieux
+ pom-operator
+ 0.0.1-SNAPSHOT
+
+
+ 0.0.1-SNAPSHOT
+
+
+
+
+ test-profile
+
+ 1.0.1
+
+
+
+
+
+
+ org.dom4j
+ dom4j
+ ${sample.version}
+
+
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-bad-pom.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-bad-pom.xml
new file mode 100644
index 000000000..bd9dfcb76
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-bad-pom.xml
@@ -0,0 +1,23 @@
+
+
+
+ 4.0.0
+
+ com.acme
+ testcode
+ 5.1
+
+
+
+
+
+
+ org.slf4j
+ slf4j-api
+ 1.7.25
+
+
+
+
\ No newline at end of file
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-child-with-broken-path-1.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-child-with-broken-path-1.xml
new file mode 100644
index 000000000..5c8086521
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-child-with-broken-path-1.xml
@@ -0,0 +1,16 @@
+
+
+ 4.0.0
+
+
+ br.com.ingenieux
+ sample-parent
+ 1
+ ../../../../../../../../../../../../../../../../../../sample-parent
+
+
+ sample-child-with-relativepath
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-child-with-broken-path-2.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-child-with-broken-path-2.xml
new file mode 100644
index 000000000..b0328f2ba
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-child-with-broken-path-2.xml
@@ -0,0 +1,16 @@
+
+
+ 4.0.0
+
+
+ br.com.ingenieux
+ sample-parent
+ 1
+ /
+
+
+ sample-child-with-relativepath
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-child-with-broken-path-3.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-child-with-broken-path-3.xml
new file mode 100644
index 000000000..a8e03c78d
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-child-with-broken-path-3.xml
@@ -0,0 +1,16 @@
+
+
+ 4.0.0
+
+
+ br.com.ingenieux
+ sample-parent
+ 1
+ C:/pom.xml
+
+
+ sample-child-with-relativepath
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-child-with-relativepath-2.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-child-with-relativepath-2.xml
new file mode 100644
index 000000000..0a50cdd82
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-child-with-relativepath-2.xml
@@ -0,0 +1,16 @@
+
+
+ 4.0.0
+
+
+ br.com.ingenieux
+ sample-parent
+ 1
+ ./sample-parent/other-pom.xml
+
+
+ sample-child-with-relativepath
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-child-with-relativepath-and-two-levels-nonloop.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-child-with-relativepath-and-two-levels-nonloop.xml
new file mode 100644
index 000000000..be5836456
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-child-with-relativepath-and-two-levels-nonloop.xml
@@ -0,0 +1,16 @@
+
+
+ 4.0.0
+
+
+ br.com.ingenieux
+ org-pom
+ 1
+ ./sample-parent/pom-with-parent-level-2-nonloop.xml
+
+
+ sample-child-with-relativepath
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-child-with-relativepath-and-two-levels.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-child-with-relativepath-and-two-levels.xml
new file mode 100644
index 000000000..6ee452f54
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-child-with-relativepath-and-two-levels.xml
@@ -0,0 +1,16 @@
+
+
+ 4.0.0
+
+
+ br.com.ingenieux
+ org-pom
+ 1
+ ./sample-parent/pom-with-parent-level-2.xml
+
+
+ sample-child-with-relativepath
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-child-with-relativepath.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-child-with-relativepath.xml
new file mode 100644
index 000000000..96bf62b22
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-child-with-relativepath.xml
@@ -0,0 +1,16 @@
+
+
+ 4.0.0
+
+
+ br.com.ingenieux
+ sample-parent
+ 1
+ ./sample-parent
+
+
+ sample-child-with-relativepath
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-parent/other-pom.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-parent/other-pom.xml
new file mode 100644
index 000000000..6127e53d8
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-parent/other-pom.xml
@@ -0,0 +1,13 @@
+
+
+ 4.0.0
+
+ br.com.ingenieux
+ sample-parent
+ 1
+
+ pom
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-parent/pom-with-parent-level-2-nonloop.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-parent/pom-with-parent-level-2-nonloop.xml
new file mode 100644
index 000000000..a31f88775
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-parent/pom-with-parent-level-2-nonloop.xml
@@ -0,0 +1,18 @@
+
+
+ 4.0.0
+
+
+ br.com.ingenieux
+ super-pom
+ 1
+ ../superpom-nonloop.xml
+
+
+ org-pom
+
+ pom
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-parent/pom-with-parent-level-2.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-parent/pom-with-parent-level-2.xml
new file mode 100644
index 000000000..29b00a599
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-parent/pom-with-parent-level-2.xml
@@ -0,0 +1,18 @@
+
+
+ 4.0.0
+
+
+ br.com.ingenieux
+ super-pom
+ 1
+ ../sample-child-with-relativepath-and-two-levels.xml
+
+
+ org-pom
+
+ pom
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-parent/pom-with-parent-level-3.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-parent/pom-with-parent-level-3.xml
new file mode 100644
index 000000000..806cf4242
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-parent/pom-with-parent-level-3.xml
@@ -0,0 +1,18 @@
+
+
+ 4.0.0
+
+
+ br.com.ingenieux
+ super-pom
+ 1
+
+
+
+ org-pom
+
+ pom
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-parent/pom-with-parent-level-4.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-parent/pom-with-parent-level-4.xml
new file mode 100644
index 000000000..7eda1ec72
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-parent/pom-with-parent-level-4.xml
@@ -0,0 +1,18 @@
+
+
+ 4.0.0
+
+
+ br.com.ingenieux
+ super-pom
+ 1
+
+
+
+ org-pom
+
+ pom
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-parent/pom.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-parent/pom.xml
new file mode 100644
index 000000000..6127e53d8
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/sample-parent/pom.xml
@@ -0,0 +1,13 @@
+
+
+ 4.0.0
+
+ br.com.ingenieux
+ sample-parent
+ 1
+
+ pom
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/superpom-nonloop.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/superpom-nonloop.xml
new file mode 100644
index 000000000..d00aa1387
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/superpom-nonloop.xml
@@ -0,0 +1,13 @@
+
+
+ 4.0.0
+
+ br.com.ingenieux
+ super-pom
+ 1
+
+ pom
+
diff --git a/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/webgoat-parent.xml b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/webgoat-parent.xml
new file mode 100644
index 000000000..4e8ab5de9
--- /dev/null
+++ b/pom-operator/src/test/resources/io/github/pixee/maven/operator/test/webgoat-parent.xml
@@ -0,0 +1,296 @@
+
+
+
+ 4.0.0
+ org.owasp.webgoat
+ webgoat-parent
+ pom
+ 8.2.3-SNAPSHOT
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.5.4
+
+
+ WebGoat Parent Pom
+ Parent Pom for the WebGoat Project. A deliberately insecure Web Application
+
+ 2006
+ https://github.com/WebGoat/WebGoat
+
+
+ OWASP
+ https://github.com/WebGoat/WebGoat/
+
+
+
+
+ GNU General Public License, version 2
+ https://www.gnu.org/licenses/gpl-2.0.txt
+
+
+
+
+
+ mayhew64
+ Bruce Mayhew
+ webgoat@owasp.org
+ OWASP
+ https://github.com/WebGoat/WebGoat
+
+
+ nbaars
+ Nanne Baars
+ nanne.baars@owasp.org
+ https://github.com/nbaars
+ Europe/Amsterdam
+
+
+ misfir3
+ Jason White
+ jason.white@owasp.org
+
+
+ zubcevic
+ René Zubcevic
+ rene.zubcevic@owasp.org
+
+
+ aolle
+ Àngel Ollé Blázquez
+ angel@olleb.com
+
+
+ jwayman
+ Jeff Wayman
+
+
+
+ dcowden
+ Dave Cowden
+
+
+
+ lawson89
+ Richard Lawson
+
+
+
+ dougmorato
+ Doug Morato
+ doug.morato@owasp.org
+ OWASP
+ https://github.com/dougmorato
+ America/New_York
+
+ https://avatars2.githubusercontent.com/u/9654?v=3&s=150
+
+
+
+
+
+
+ OWASP WebGoat Mailing List
+ https://lists.owasp.org/mailman/listinfo/owasp-webgoat
+ Owasp-webgoat-request@lists.owasp.org
+ owasp-webgoat@lists.owasp.org
+ http://lists.owasp.org/pipermail/owasp-webgoat/
+
+
+
+
+ https://github.com/WebGoat/WebGoat
+ scm:git:git@github.com:WebGoat/WebGoat.git
+ scm:git:git@github.com:WebGoat/WebGoat.git
+ HEAD
+
+
+
+ Github Issues
+ https://github.com/WebGoat/WebGoat/issues
+
+
+
+
+ UTF-8
+ UTF-8
+ 17
+ 17
+
+
+ 2.5.2
+ 3.2.1
+ 3.12.0
+ 2.6
+ 30.1-jre
+ 1.18.20
+ 2.27.2
+ 3.8.0
+ 2.22.0
+ 3.1.2
+ 3.1.1
+ 3.1.0
+ 3.0.0-M5
+ 17
+
+
+
+ webgoat-container
+ webgoat-lessons
+ webgoat-server
+ webwolf
+ webgoat-integration-tests
+ docker
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.projectlombok
+ lombok
+ provided
+ true
+
+
+ org.apache.commons
+ commons-exec
+ 1.3
+
+
+ javax.xml.bind
+ jaxb-api
+
+
+
+
+
+
+ org.codehaus.mojo
+ flatten-maven-plugin
+ 1.2.5
+
+
+
+
+ flatten
+ process-resources
+
+ flatten
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${maven-compiler-plugin.version}
+
+
+ 15
+ UTF-8
+
+
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ 3.1.2
+
+ UTF-8
+ true
+ true
+ config/checkstyle/checkstyle.xml
+ config/checkstyle/suppressions.xml
+ checkstyle.suppressions.file
+
+
+
+
+ org.apache.maven.plugins
+ maven-pmd-plugin
+ 3.14.0
+
+ 15
+ 1
+
+
+
+ ${maven.multiModuleProjectDirectory}/config/pmd/pmd-ruleset.xml
+
+
+ true
+ true
+
+
+
+
+ check
+
+
+
+
+
+
+
+
+
+ owasp
+
+ false
+
+
+
+
+ org.owasp
+ dependency-check-maven
+ 6.1.3
+
+ 7
+ true
+ true
+
+
+
+ ${maven.multiModuleProjectDirectory}/config/dependency-check/project-suppression.xml
+
+
+
+
+
+
+ check
+
+
+
+
+
+
+
+
+
+
+
+ central
+ https://repo.maven.apache.org/maven2
+
+ false
+
+
+
+
+
+ central
+ https://repo.maven.apache.org/maven2
+
+ false
+
+
+
+
+
+
diff --git a/pom-operator/src/test/resources/simplelogger.properties b/pom-operator/src/test/resources/simplelogger.properties
new file mode 100644
index 000000000..a014e30b5
--- /dev/null
+++ b/pom-operator/src/test/resources/simplelogger.properties
@@ -0,0 +1,2 @@
+# suppress inspection "UnusedProperty"
+org.slf4j.simpleLogger.defaultLogLevel=debug
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 29dee6ed2..75ff1a075 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -47,3 +47,4 @@ include("plugins:codemodder-plugin-llm")
include("plugins:codemodder-plugin-maven")
include("plugins:codemodder-plugin-pmd")
include("plugins:codemodder-plugin-semgrep")
+include("pom-operator")