diff --git a/.gitignore b/.gitignore
index f79c9285cd4c..cd0121172e71 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@ target/
.classpath
.settings/
.svn/
+.cache/
bin/
# Intellij
*.ipr
diff --git a/Documentation/CACHE-HOWTO.md b/Documentation/CACHE-HOWTO.md
new file mode 100644
index 000000000000..aa15f626af65
--- /dev/null
+++ b/Documentation/CACHE-HOWTO.md
@@ -0,0 +1,204 @@
+
+
+### Overview
+
+Cache configuration provides you additional control over incremental maven behavior. Follow it step by step to
+understand how it works and figure out your optimal config
+
+### Minimal config
+
+Absolutely minimal config which enables incremental maven with local cache
+
+```xml
+
+
+
+
+ true
+ XX
+
+
+
+
+ {*.java,*.xml,*.properties}
+
+
+
+```
+
+### Enabling remote cache
+
+Just add `` section under ``
+
+```xml
+
+
+ true
+ XX
+
+ https://yourserver:port
+
+
+```
+
+### Adding more file types to input
+
+Add all the project specific source code files in ``. Scala in this case:
+
+```xml
+
+
+
+ {*.java,*.xml,*.properties,*.scala}
+
+
+```
+
+### Adding source directory for bespoke project layouts
+
+In most of the cases incremental maven will recognize directories automatically by build introspection. If not, you can
+add additional directories with ``. Also you can filter out undesirable dirs and files by using exclude tag
+
+```xml
+
+
+
+ {*.java,*.xml,*.properties,*.scala}
+ importantdir/
+ tempfile.out
+
+
+```
+
+### Plugin property is env specific (breaks checksum and caching)
+
+Consider to exclude env specific properties:
+
+```xml
+
+
+
+ ...
+
+
+
+ argLine
+
+
+
+```
+
+Implications - builds with different `argLine` will have identical checksum. Validate that is semantically valid.
+
+### Plugin property points to directory where only subset of files is relevant
+
+If plugin configuration property points to `somedir` it will be scanned with default glob. You can tweak it with custom
+processing rule
+
+```xml
+
+
+
+ ...
+
+
+
+
+
+
+
+
+```
+
+### Local repository is not updated because `install` is cached
+
+Add `executionControl/runAlways` section
+
+```xml
+
+
+
+ ...
+
+
+ ...
+
+
+
+
+
+ unpack-autoupdate
+
+
+ install
+
+
+
+
+```
+
+### I occasionally cached build with `-DskipTests=true` and tests do not run now
+
+If you add command line flags to your build, they do not participate in effective pom - maven defers final value
+resolution to plugin runtime. To invalidate build if filed value is different in runtime, add reconciliation section
+to `executionControl`:
+
+```xml
+
+
+
+ ...
+
+
+
+
+
+
+
+
+
+
+```
+
+Please notice `skipValue` attribute. It denotes value which forces skipped execution.
+Read `propertyName="skipTests" skipValue="true"` as if property skipTests has value true, plugin will skip execution If
+you declare such value incremental maven will reuse appropriate full-build though technically they are different, but
+because full-build is better it is safe to reuse
+
+### How to renormalize line endings in working copy after committing .gitattributes (git 2.16+)
+
+Ensure you've committed (and ideally pushed everything) - no changes in working copy. After that:
+
+```shell
+# Rewrite objects and update index
+git add --renormalize .
+# Commit changes
+git commit -m "Normalizing line endings"
+# Remove working copy paths from git cache
+git rm --cached -r .
+# Refresh with new line endings
+git reset --hard
+```
+
+### I want to cache interim build and override it later with final version
+
+Solution: set `-Dremote.cache.save.final=true` to nodes which produce final builds. Such builds will not be overridden
+and eventually will replace all interim builds
\ No newline at end of file
diff --git a/Documentation/CACHE-PARAMETERS.md b/Documentation/CACHE-PARAMETERS.md
new file mode 100644
index 000000000000..7633ddbea158
--- /dev/null
+++ b/Documentation/CACHE-PARAMETERS.md
@@ -0,0 +1,52 @@
+
+
+# Overview
+
+This documents contains various configuration parameters supported by cache engine
+
+## Command line flags
+
+| Parameter | Description | Usage Scenario |
+| ----------- | ----------- | ----------- |
+| `-Dremote.cache.configPath=true/false` | Location of cache configuration file | Cache config is not in default location |
+| `-Dremote.cache.enabled=true/false` | Remote cache and associated features disabled/enabled | To remove noise from logs then remote cache is not available |
+| `-Dremote.cache.save.enabled=true/false` | Remote cache save allowed or not | To designate nodes which allowed to push in remote shared cache |
+| `-Dremote.cache.save.final=true/false` | Is it allowed or not to override produced cache | To ensure that reference build is not overriden by interim build |
+| `-Dremote.cache.failFast=true/false` | Fail on the first module whcih cannot be restored from cache | Remote cache setup/tuning/troubleshooting |
+| `-Dremote.cache.baselineUrl=` | Location of baseline build for comparison | Remote cache setup/tuning/troubleshooting |
+
+## Project level properties
+
+Project level parameters allow overriding global parameters on project level Must be specified as project properties:
+
+```xml
+
+
+ ...
+
+ {*.css}
+
+
+```
+
+| Parameter | Description |
+| ----------------------------- | ----------- |
+| `remote.cache.input.glob` | Project specific glob to select sources. Overrides global glob. |
+| `remote.cache.input` | Property prefix to mark paths which must be additionally scanned for source code. Value of property starting with this prefix will be treated as path relatively to current project/module |
+| `remote.cache.exclude` | Property prefix to mark paths which must be excluded from source code search. Value of property starting with this prefix will be treated as path to current project/module |
+| `remote.cache.processPlugins` | Introspect plugins to find inputs or not. Default is true. |
diff --git a/Documentation/CACHE-REMOTE.md b/Documentation/CACHE-REMOTE.md
new file mode 100644
index 000000000000..f5fe38721d70
--- /dev/null
+++ b/Documentation/CACHE-REMOTE.md
@@ -0,0 +1,249 @@
+
+
+# Overview
+
+This document describes generic approach to cache setup. The process require expertise in maven equivalent to expertise
+required to author and Maven your project build, it implies good knowledge of both Maven and the project. Due to Maven
+model limitation the process is manual, but allows you to achieve sufficient control and transparency over caching
+logic.
+
+# Step-By-Step
+
+## Fork branch for cache setup purposes
+
+It is recommended to fork branch for cache setup purposes as you might need to do changes to project build as long as
+you go.
+
+## Setup http server to store artifacts
+
+In order to share build results you need shared storage. Basically any http server which supports http PUT/GET/HEAD
+operations will work. In our case it is a Nginx OSS with file system module. Add the url to config and
+change `remote@enabled` to true:
+
+```xml
+
+
+ http://your-buildcache-url
+
+```
+
+Known limitations: auth is not supported yet
+
+## Build selection
+
+In order to share build results, you need a golden source of builds. Build stored in cache ideally should be a build
+assembled in the most correct, comprehensive and complete way. In such a case you can make sure that whoever reuses such
+build doesn't compromise quality of own build. Often per pull requests builds are the best candidates to populate cache
+because they seed cache fast and provide sufficient quality safeguards.
+
+## CI Build setup to seed shared cache
+
+In order to share build results, you need to store builds in the shared storage. Default scheme is to configure
+designated CI nodes only to push in cache and prohibit elsewhere. In order to allow writes in remote cache add jvm
+property to designated CI builds.
+
+```
+-Dremote.cache.save.enabled=true
+```
+
+Run your branch, review log and ensure that artifacts are uploaded to remote cache. Now, rerun build and ensure that it
+completes almost instantly because it is fully cached. Hint: consider limiting first attempts to single agent/node to
+simplify troubleshooting.
+
+## Normalize local builds to reuse CI build cache
+
+As practice shows, developers often don't realize that builds they run in local and CI environments are different. So
+straightforward attempt to reuse remote cache in local build usually results in cache misses because of difference in
+plugins, parameters, profiles, environment, etc. In order to reuse results you might need to change poms, cache config,
+CI jobs and the project itself. This part is usually most challenging and time-consuming. Follow steps below to
+iteratively achieve working configuration.
+
+### Before you start
+
+Before you start, please keep in mind basic principles:
+
+* Cache is checksum based, it is a complex hash function essentially. In order to to produce the same hash the source
+ code, effective poms and dependencies should match.
+* There is no built-in normalization of line endings in this implementation, file checksum calculation is raw bytes
+ based. The most obvious implication could be illustrated by a simple Git checkout. By default git will check out
+ source code with CRLF line endings on win and LF on Linux. Because of that builds over same commit on a Linux agent
+ and local build on Windows workstation will yield different checksums.
+* Parameters of plugins are manually tracked ony. For example to avoid of accidentally reusing builds which never run
+ tests ensure that critical surfire parameters are tracked (`skipTests` and similar) in config. The same applies for
+ all over plugins.
+
+### Configure local build in debug mode
+
+To minimize distractions and simplify understanding of discrepancies following is recommended:
+
+* Run build with single threaded builder to make sure logs from different modules do not interfere
+* Enable cache fail fast mode to focus on the blocking failure
+* Provide reference to the CI build as a baseline for comparison between your local and remote builds. Go to the
+ reference CI build and one of the final lines of the build should be
+
+```
+[INFO] [CACHE][artifactId] Saved to remote cache https://your-cache-url/<...>/915296a3-4596-4eb5-bf37-f6e13ebe087e/cache-report.xml.
+```
+
+followed by a link to a `cache-report.xml` file. The `cache-report.xml` contains aggregated information about the
+produced cache and could be used as a baseline for comparison.
+
+* Run local build. Command line should look similar to this:
+
+```bash
+mvnw verify -Dremote.cache.failFast=true -Dremote.cache.baselineUrl=https://url-from-ci-build-to-cache-report.xml
+```
+
+Once discrepancy between remote and local builds detected cache will fail with diagnostic info
+in `target/incremental-maven` directory:
+
+```
+* buildinfo-baseline-3c64673e23259e6f.xml - build specficiation from baseline build
+* buildinfo-db43936e0666ce7.xml - build specification of locall build
+* buildsdiff.xml - comparison report with list of discrepancies
+```
+
+Review `buildsdiff.xml` file and eliminate detected discrepancies.You can also diff build-info files directly to get low
+level insights. See techniques to configure cache in [How-To](CACHE-HOWTO.md) and troubleshooting of typical issues in
+the section below.
+
+# Common issues
+
+## Issue 1: Local checkout is with different line endings
+
+Solution: normalise line endings. Current implementation doesn't have built-in line endings normalization, it has to be
+done externally. In git it is recommended to use `.gitattributes` file to establish consistent line endings across all
+envs for file types specific to this project
+
+## Issue 2: Effective poms mismatch because of plugins filtering by profiles
+
+Different profiles between remote and local builds results in different text of effective poms and break checksums.
+Solution: instead of adding/removing specific plugins from build altogether with profiles use profile specific `skip`
+or `disabled` flags instead. Instead of:
+
+ ```
+
+
+ run-plugin-in-ci-only
+
+
+
+ surefire-report-maven-plugin
+
+
+
+
+
+
+
+
+ ```
+
+Use:
+
+ ```xml
+
+
+
+ true
+
+
+
+
+ maven-surefire-plugin
+
+
+ ${skip.plugin.property}
+
+
+
+
+
+
+ run-plugin-in-ci-only
+
+
+ false
+
+
+
+ ```
+
+Hint: effective poms could be found in `buildinfo` files under `/build/projectsInputInfo/item[@type='pom']`
+xpath (`item type="pom"`).
+
+## Issue 3: Effective pom mismatch because of environment specific properties
+
+Potential reason: Sometimes it is not possible to avoid discrepancies in different environments - for example if you
+need to invoke command line command, it will be likely different on win and linux. Such commands will appear in
+effective pom as a different literal values and will result in checksum mismatch Solution: filter out such properties
+from cache effective pom:
+
+```xml
+
+
+
+ ...
+
+
+
+ argLine
+
+
+
+```
+
+## Issue 4: Unexpected or transient files in checksum calculation
+
+Potential reasons: plugins or tests emit temporary files (logs and similar) in non-standard locations Solution: adjust
+global exclusions list to filter out unexpected files:
+
+```
+
+ tempfile.out
+
+```
+
+see sample config for exact syntax
+
+## Issue 5: Difference in tracked plugin properties
+
+Tracked property in config means it is critical for determining is build up to date or not. Discrepancies could happen
+for any plugin for a number of reasons. Example: local build is using java target 1.6, remote: 1.8. `buildsdiff.xml`
+will produce something like
+
+```
+
+```
+
+Solution is at your discretion. If the property is tracked, out-of-date status is fair and expected. If you want to
+relax consistency rules in favor of compatibility, remove property from tracked list
+
+## Issue 5: Version changes invalidate effective pom checksum
+
+Current implementation doesn't support version changes between cache entries. It will result in cache invalidation for
+each new version.
+To mitigate the issue please consider migrating off traditional maven release approach - try to use single version id in
+project (eg `MY-PROJECT-LOCAL`). Such approach simplifies git branching workflow significantly.
+
+Deployment of artifacts with specific version from builds with cache is not supported yet.
+
diff --git a/Documentation/CACHE.md b/Documentation/CACHE.md
new file mode 100644
index 000000000000..391becc7b5f7
--- /dev/null
+++ b/Documentation/CACHE.md
@@ -0,0 +1,170 @@
+
+
+## Overview
+
+Idea of Incremental Maven is to specify module inputs and outputs and make them known to standard maven core. This
+allows accurate analysis and determination of out-of-date build artifacts in the build dependencies graph. Making the
+dependency graph analysis deterministic leads to improvements in build times by avoiding re-building unnecessary
+modules.
+Cache does not make any low-level interventions to build process and delegates actual build work to maven core. This
+guarantees that build results are identical to results produced by standard maven and are fully reproducible.
+To achieve accurate input and outputs calculation incremental maven combines automatic introspection
+of [project object model](https://maven.apache.org/pom.html#What_is_the_POM) in conjunction with configuration-driven
+rules for fine-grained content and execution control. For content analysis it digests based approach which is more
+reliable over widely used file timestamps in tools like Make or Apache Ant. Deterministic build state allows reliably
+cache even intermediate outputs of build and share them between teams using remote cache. Deterministic inputs
+calculation allows distributed and parallel builds running in heterogeneous environments (like cloud of build agents)
+could efficiently reuse cached build artifacts. Therefore incremental maven is particularly well-suited for large maven
+projects that have significant number of small modules. Remote cache in conjunction with precise input identification
+effectively enables "change once - build once" approach.
+
+### Maven insights
+
+The challenge of implementing build cache in Maven is that domain model is overly generic and doesn't support well
+reproducible builds. You might have never thought of that, but it is a reality that 2 different Maven builds from the
+same source code normally produce 2 different results. The question here is tolerance level - can you accept particular
+discrepancies or not. For most of teams artifacts produced in the same build environment from the same source code will
+be considered equivalent and technical differences between them (like different timestamps in jar manifests) could be
+ignored. Now consider scenario when artifact is first produced with compiler X and cached but later without touching a
+update compiler changes to Y and yields significantly different outcomes of compilation. Ask yourself a question \- am I
+consider artifacts of such builds equivalent? Both Yes and No outcomes are pretty possible and could be even desirable
+in different scenarios. When productivity and performance are the primary concerns it could be desirable to tolerate
+insignificant discrepancies and maximise reuse of cached builds. As long as correctness in focus there could be demand
+to comply with the exact release process. In the same way as with classic Maven, decision stays with you - what is
+acceptable difference between builds. In the same way as with classic Maven the previous build is just an approximation
+of today build with some tolerance (implementation, configuration and environment driven).
+
+### Implementation insights
+
+At very simple form, the incremental maven is essentially a hash function which takes maven project and produces hash
+code (checksum). Then hash value is used to fetch and restore build result.
+As with any hash, there could be collisions and instabilities. Collision could happen if the same hash produced from the
+different build states and will result in unintended reuse. Instability means that same input yields different hash sums
+in different runs - resulting in cache miss. The ultimate target is to achieve desired balance between collisions (
+sufficient correctness) and stability (sufficient reuse). In current implementation this is achieved by configuring
+project specific processing rules in static configuration file. To avoid unintentional collisions and achieve better
+correctness need to ensure that every critical file and plugin parameter accounted in build inputs. In order to achieve
+better reuse need to ensure that non-critical files (test logs, readme and similar) and non-critical plugin parameters (
+like number of threads in build) are filtered out from build inputs. Essentially cache configuration is a process of
+inspecting build, taking these decision and reflect them in the cache configuration.
+
+Please notice though idea of perfectly matching builds might be tempting, but it is not practical with regard to
+caching. Perfect correctness means that not a single build could be reused and renders whole idea of builds caching
+useless. Whatever build tool you use, there will be always a tradeoff which might be acceptable or not in particular
+situation. Incremental Maven provides flexible and transparent control over caching policy and allows achieving desired
+outcomes - maximize reusability or maximize equivalence between pre-cached candidates and requested builds.
+
+## Usage
+
+Cornerstone principle of using this tool is that it is delivered as is. Though the tool went through thorough
+verification it's still consumer's responsibility to verify final product quality.
+
+### Recommended Scenarios
+
+Given all the information above, the Incremental Maven is recommended to use in scenarios when productivity and
+performance are in priority. Typical cases are:
+
+* Speedup CI. In conjunction with remote cache incremental maven could drastically reduce build times, validate pull
+ requests faster and reduce load on CI nodes
+* Speedup developer builds. By reusing cached builds developers could verify changes much faster and be more productive.
+ No more `-DskipTests` and similar.
+* Assemble artifacts faster. In some development models it might be critical to have as fast build/deploy cycle as
+ possible. Caching helps to cut down time drastically in such scenarios because it doesn't require to build cached
+ dependencies.
+
+For cases there correctness must be ensured (eg prod builds), it is recommended to disable cache and do clean builds.
+This also allows you to validate cache correctness and reconcile cache outcomes on CI process.
+
+## Getting Started
+
+To on-board incremental maven you need to complete several steps:
+
+* Get incremental maven distribution
+* Add cache config in .mvn
+* Validate build results and iteratively adjust config to project specifics
+* Migrate CI to incremental maven with remote cache (to get full benefit) - optional
+
+### Get incremental maven distribution
+
+The recommended way is to add [Takari Maven Wrapper](https://github.com/takari/maven-wrapper) to your project. In that
+case `maven-wrapper.properties` should reference the latest incremental maven distribution:
+
+```properties
+distributionUrl=https://your-server/maven-incremental.zip
+wrapperUrl=https://your-server/maven-wrapper-0.5.5.jar
+```
+
+Benefits of using maven wrapper are following:
+
+* simple distribution across workstations and CI envs
+* maven stays compatible to your branch
+* further upgrades are simplified significantly
+ If you refuse wrapper - then download, unzip and install it just as usual maven. Further it will be assumed you use
+ maven wrapper (`mvnw`)
+
+### Adding cache config
+
+Copy [default config](maven-cache-config.xml) and [xml schema](../maven-core/src/main/resources/cache-config.xsd)
+to [.mvn](https://maven.apache.org/configure.html) dir of yor project.
+To get overall understanding of cache machinery it is recommended to review the config and read comments. In typical
+scenario you need to adjust:
+
+* remote cache location
+* source code files glob
+* plugins reconciliation rules - add critical plugin parameters to reconciliation
+* add non-standard source code locations (most of locations discovered automatically from project and plugins config,
+ but still there might be edge cases)
+
+See also:
+
+* [Remote cache setup](CACHE-REMOTE.md) - instruction how to setup shared cache
+* [Cache How-To](CACHE-HOWTO.md) - cookbook for frequently encountered questions
+* [Cache Parameters](CACHE-PARAMETERS.md) - description of supported parameters
+* Attached [sample config file](maven-cache-config.xml) and elements annotations in xsd schema. (Ctrl+Q in idea should
+ show annotations in popup)
+
+### Adjusting cache config
+
+Having incremental maven and the config in place you're all set. To run first cacheable build just
+execute: `mvnw clean install`
+
+* Ensure that the config is picked up and incremental maven is picked up. Just check log output - you will notice cache
+ related output or initialization error message.
+* Navigate to your local repo directory - there should be sibling next to your local repo named `cache`
+* Find `buildinfo.xml` for typical module and review it. Ensure that
+ * expected source code files are present in build info
+ * all critical plugins and their critical parameters are covered by config
+
+Notice - in configuration you should find best working trade-off between fairness and cache efficiency. Adding
+unnecessary rules and checks could reduce both performance and cache efficiency (hit rate).
+
+### Adding caching CI and remote cache
+
+To leverage remote cache feature you need web server which supports get/put operations
+like [Nginx OSS](http://nginx.org/en/) (with fs module) or binary repo in Artifactory. It is recommended to populate
+remote cache from CI build. Benefits are:
+
+* such scheme provides predictable and consistent artifacts in remote cache
+* usually CI builds project fast enough to populate cache for team members See [Remote cache setup](CACHE-REMOTE.md) for
+ detailed description of cache setup
+
+## Credits
+CacheConfigImpl
+* Maximilian Novikov - Project lead. Idea, design, coordination and verification.
+* Alexander Ashitkin - Co-design and implementation of the majority of functionality
+* Alexander Novoselov - Hashing module implementation
\ No newline at end of file
diff --git a/Documentation/maven-cache-config.xml b/Documentation/maven-cache-config.xml
new file mode 100644
index 000000000000..a2a7ad8ee1c3
--- /dev/null
+++ b/Documentation/maven-cache-config.xml
@@ -0,0 +1,102 @@
+
+
+
+
+
+ true
+ SHA-256
+ true
+
+ PROJECT-LOCAL
+
+
+ http://host:port
+
+
+ 3
+
+
+
+
+
+
+ {*.java,*.groovy,*.yaml,*.svcd,*.proto,*assembly.xml,assembly*.xml,*logback.xml,*.vm,*.ini,*.jks,*.properties,*.sh,*.bat}
+
+ src/
+ pom.xml
+
+
+
+ 111
+
+
+
+
+
+
+
+ 1
+ 2
+
+
+
+
+
+
+
+
+
+
+ my-execution-id
+
+
+ install
+
+
+ deploy
+
+
+ deploy-local
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/README.md b/README.md
index 0c77dabf55ae..88b0a333f257 100644
--- a/README.md
+++ b/README.md
@@ -29,6 +29,11 @@ build, reporting and documentation from a central piece of information.
If you think you have found a bug, please file an issue in the [Maven Issue Tracker](https://issues.apache.org/jira/browse/MNG).
+Incremental Build and Cache
+-------------
+This maven version supports Incremental Build feature which calculates out-of-date modules in the build dependencies graph and improves build times by avoiding re-building unnecessary modules.
+Read [cache guide](Documentation/CACHE.md) for more details
+
Documentation
-------------
diff --git a/apache-maven/src/main/appended-resources/META-INF/LICENSE.vm b/apache-maven/src/main/appended-resources/META-INF/LICENSE.vm
index 25ac46f3770f..6ac9f31124b7 100644
--- a/apache-maven/src/main/appended-resources/META-INF/LICENSE.vm
+++ b/apache-maven/src/main/appended-resources/META-INF/LICENSE.vm
@@ -18,13 +18,14 @@
##
-Apache Maven includes a number of components and libraries with separate
-copyright notices and license terms. Your use of those components are
-subject to the terms and conditions of the following licenses:
+Apache Maven includes a number of components and libraries with separate
+copyright notices and license terms. Your use of those components are
+subject to the terms and conditions of the following licenses:
##
#set ( $apacheMavenGroupIds = [ "org.apache.maven", "org.apache.maven.wagon", "org.apache.maven.resolver",
"org.apache.maven.shared" ] )
#set ( $MITLicenseNames = [ "MIT License", "MIT license", "The MIT License" ] )
+#set ( $CDDLLicenseNames = [ "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0", "CDDL/GPLv2+CE", "CDDL 1.1", "GPL2 w/ CPE" ] )
#foreach ( $project in $projects )
#**##foreach ( $license in $project.licenses)
#* *##set ( $groupId = $project.artifact.groupId )
@@ -32,8 +33,8 @@ subject to the terms and conditions of the following licenses:
#* *##if ( !$apacheMavenGroupIds.contains( $groupId ) )
#* *### advertise about each non-Maven dependency
#* *###
-#* *### infer SPDX license code
-#* *##if ( $license.name == "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0" )
+#* *### infer SPDX license id
+#* *##if ( $CDDLLicenseNames.contains( $license.name ) )
#* *##set ( $spdx = 'CDDL-1.0' )
#* *##elseif ( $MITLicenseNames.contains( $license.name ) )
#* *##set ( $spdx = 'MIT' )
@@ -41,9 +42,17 @@ subject to the terms and conditions of the following licenses:
#* *##set ( $spdx = 'EPL-1.0' )
#* *##elseif ( $license.url.contains( "www.apache.org/licenses/LICENSE-2.0" ) )
#* *##set ( $spdx = 'ASL-2.0' )
+#* *##elseif ( $license.url.contains( "www.eclipse.org/org/documents/edl-v10.php" ) )
+#* *##set ( $spdx = 'EDL-1.0' )
+#* *##elseif ( $license.url.contains( "www.mozilla.org/en-US/MPL/1.1" ) )
+#* *##set ( $spdx = 'MPL-1.1' )
+#* *##elseif ( $license.url.contains( "www.gnu.org/licenses/gpl" ) )
+#* *##set ( $spdx = 'GPL-3.0' )
+#* *##elseif ( $license.url.contains( "www.gnu.org/licenses/lgpl" ) )
+#* *##set ( $spdx = 'LGPL-3.0' )
#* *##else
#* *### unrecognized license will require analysis to know obligations
-#* *##set ( $spdx = 'unrecognized' )
+#* *##set ( $spdx = "unrecognized: $license.name $license.url" )
#* *##end
#* *###
#* *### fix project urls that are wrong in pom
diff --git a/apache-maven/src/main/appended-resources/licenses/EDL-1.0.txt b/apache-maven/src/main/appended-resources/licenses/EDL-1.0.txt
new file mode 100644
index 000000000000..259e0377f30f
--- /dev/null
+++ b/apache-maven/src/main/appended-resources/licenses/EDL-1.0.txt
@@ -0,0 +1,23 @@
+Eclipse Distribution License - v 1.0
+Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors.
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided
+that the following conditions are met:
+
+- Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+- Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation and/or other materials provided
+with the distribution.
+- Neither the name of the Eclipse Foundation, Inc. nor the names of its contributors may be used to endorse
+or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/apache-maven/src/main/appended-resources/licenses/GPL-3.0.txt b/apache-maven/src/main/appended-resources/licenses/GPL-3.0.txt
new file mode 100644
index 000000000000..e72bfddabc15
--- /dev/null
+++ b/apache-maven/src/main/appended-resources/licenses/GPL-3.0.txt
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
\ No newline at end of file
diff --git a/apache-maven/src/main/appended-resources/licenses/LGPL-3.0.txt b/apache-maven/src/main/appended-resources/licenses/LGPL-3.0.txt
new file mode 100644
index 000000000000..153d416dc8d2
--- /dev/null
+++ b/apache-maven/src/main/appended-resources/licenses/LGPL-3.0.txt
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
\ No newline at end of file
diff --git a/apache-maven/src/main/appended-resources/licenses/MPL-1.1.txt b/apache-maven/src/main/appended-resources/licenses/MPL-1.1.txt
new file mode 100644
index 000000000000..7bcf5c0e2885
--- /dev/null
+++ b/apache-maven/src/main/appended-resources/licenses/MPL-1.1.txt
@@ -0,0 +1,472 @@
+http://www.mozilla.org/MPL/MPL-1.1.html
+
+
+ MOZILLA PUBLIC LICENSE
+ Version 1.1
+
+ ---------------
+
+1. Definitions.
+
+ 1.0.1. "Commercial Use" means distribution or otherwise making the
+ Covered Code available to a third party.
+
+ 1.1. "Contributor" means each entity that creates or contributes to
+ the creation of Modifications.
+
+ 1.2. "Contributor Version" means the combination of the Original
+ Code, prior Modifications used by a Contributor, and the Modifications
+ made by that particular Contributor.
+
+ 1.3. "Covered Code" means the Original Code or Modifications or the
+ combination of the Original Code and Modifications, in each case
+ including portions thereof.
+
+ 1.4. "Electronic Distribution Mechanism" means a mechanism generally
+ accepted in the software development community for the electronic
+ transfer of data.
+
+ 1.5. "Executable" means Covered Code in any form other than Source
+ Code.
+
+ 1.6. "Initial Developer" means the individual or entity identified
+ as the Initial Developer in the Source Code notice required by Exhibit
+ A.
+
+ 1.7. "Larger Work" means a work which combines Covered Code or
+ portions thereof with code not governed by the terms of this License.
+
+ 1.8. "License" means this document.
+
+ 1.8.1. "Licensable" means having the right to grant, to the maximum
+ extent possible, whether at the time of the initial grant or
+ subsequently acquired, any and all of the rights conveyed herein.
+
+ 1.9. "Modifications" means any addition to or deletion from the
+ substance or structure of either the Original Code or any previous
+ Modifications. When Covered Code is released as a series of files, a
+ Modification is:
+ A. Any addition to or deletion from the contents of a file
+ containing Original Code or previous Modifications.
+
+ B. Any new file that contains any part of the Original Code or
+ previous Modifications.
+
+ 1.10. "Original Code" means Source Code of computer software code
+ which is described in the Source Code notice required by Exhibit A as
+ Original Code, and which, at the time of its release under this
+ License is not already Covered Code governed by this License.
+
+ 1.10.1. "Patent Claims" means any patent claim(s), now owned or
+ hereafter acquired, including without limitation, method, process,
+ and apparatus claims, in any patent Licensable by grantor.
+
+ 1.11. "Source Code" means the preferred form of the Covered Code for
+ making modifications to it, including all modules it contains, plus
+ any associated interface definition files, scripts used to control
+ compilation and installation of an Executable, or source code
+ differential comparisons against either the Original Code or another
+ well known, available Covered Code of the Contributor's choice. The
+ Source Code can be in a compressed or archival form, provided the
+ appropriate decompression or de-archiving software is widely available
+ for no charge.
+
+ 1.12. "You" (or "Your") means an individual or a legal entity
+ exercising rights under, and complying with all of the terms of, this
+ License or a future version of this License issued under Section 6.1.
+ For legal entities, "You" includes any entity which controls, is
+ controlled by, or is under common control with You. For purposes of
+ this definition, "control" means (a) the power, direct or indirect,
+ to cause the direction or management of such entity, whether by
+ contract or otherwise, or (b) ownership of more than fifty percent
+ (50%) of the outstanding shares or beneficial ownership of such
+ entity.
+
+2. Source Code License.
+
+ 2.1. The Initial Developer Grant.
+ The Initial Developer hereby grants You a world-wide, royalty-free,
+ non-exclusive license, subject to third party intellectual property
+ claims:
+ (a) under intellectual property rights (other than patent or
+ trademark) Licensable by Initial Developer to use, reproduce,
+ modify, display, perform, sublicense and distribute the Original
+ Code (or portions thereof) with or without Modifications, and/or
+ as part of a Larger Work; and
+
+ (b) under Patents Claims infringed by the making, using or
+ selling of Original Code, to make, have made, use, practice,
+ sell, and offer for sale, and/or otherwise dispose of the
+ Original Code (or portions thereof).
+
+ (c) the licenses granted in this Section 2.1(a) and (b) are
+ effective on the date Initial Developer first distributes
+ Original Code under the terms of this License.
+
+ (d) Notwithstanding Section 2.1(b) above, no patent license is
+ granted: 1) for code that You delete from the Original Code; 2)
+ separate from the Original Code; or 3) for infringements caused
+ by: i) the modification of the Original Code or ii) the
+ combination of the Original Code with other software or devices.
+
+ 2.2. Contributor Grant.
+ Subject to third party intellectual property claims, each Contributor
+ hereby grants You a world-wide, royalty-free, non-exclusive license
+
+ (a) under intellectual property rights (other than patent or
+ trademark) Licensable by Contributor, to use, reproduce, modify,
+ display, perform, sublicense and distribute the Modifications
+ created by such Contributor (or portions thereof) either on an
+ unmodified basis, with other Modifications, as Covered Code
+ and/or as part of a Larger Work; and
+
+ (b) under Patent Claims infringed by the making, using, or
+ selling of Modifications made by that Contributor either alone
+ and/or in combination with its Contributor Version (or portions
+ of such combination), to make, use, sell, offer for sale, have
+ made, and/or otherwise dispose of: 1) Modifications made by that
+ Contributor (or portions thereof); and 2) the combination of
+ Modifications made by that Contributor with its Contributor
+ Version (or portions of such combination).
+
+ (c) the licenses granted in Sections 2.2(a) and 2.2(b) are
+ effective on the date Contributor first makes Commercial Use of
+ the Covered Code.
+
+ (d) Notwithstanding Section 2.2(b) above, no patent license is
+ granted: 1) for any code that Contributor has deleted from the
+ Contributor Version; 2) separate from the Contributor Version;
+ 3) for infringements caused by: i) third party modifications of
+ Contributor Version or ii) the combination of Modifications made
+ by that Contributor with other software (except as part of the
+ Contributor Version) or other devices; or 4) under Patent Claims
+ infringed by Covered Code in the absence of Modifications made by
+ that Contributor.
+
+3. Distribution Obligations.
+
+ 3.1. Application of License.
+ The Modifications which You create or to which You contribute are
+ governed by the terms of this License, including without limitation
+ Section 2.2. The Source Code version of Covered Code may be
+ distributed only under the terms of this License or a future version
+ of this License released under Section 6.1, and You must include a
+ copy of this License with every copy of the Source Code You
+ distribute. You may not offer or impose any terms on any Source Code
+ version that alters or restricts the applicable version of this
+ License or the recipients' rights hereunder. However, You may include
+ an additional document offering the additional rights described in
+ Section 3.5.
+
+ 3.2. Availability of Source Code.
+ Any Modification which You create or to which You contribute must be
+ made available in Source Code form under the terms of this License
+ either on the same media as an Executable version or via an accepted
+ Electronic Distribution Mechanism to anyone to whom you made an
+ Executable version available; and if made available via Electronic
+ Distribution Mechanism, must remain available for at least twelve (12)
+ months after the date it initially became available, or at least six
+ (6) months after a subsequent version of that particular Modification
+ has been made available to such recipients. You are responsible for
+ ensuring that the Source Code version remains available even if the
+ Electronic Distribution Mechanism is maintained by a third party.
+
+ 3.3. Description of Modifications.
+ You must cause all Covered Code to which You contribute to contain a
+ file documenting the changes You made to create that Covered Code and
+ the date of any change. You must include a prominent statement that
+ the Modification is derived, directly or indirectly, from Original
+ Code provided by the Initial Developer and including the name of the
+ Initial Developer in (a) the Source Code, and (b) in any notice in an
+ Executable version or related documentation in which You describe the
+ origin or ownership of the Covered Code.
+
+ 3.4. Intellectual Property Matters
+ (a) Third Party Claims.
+ If Contributor has knowledge that a license under a third party's
+ intellectual property rights is required to exercise the rights
+ granted by such Contributor under Sections 2.1 or 2.2,
+ Contributor must include a text file with the Source Code
+ distribution titled "LEGAL" which describes the claim and the
+ party making the claim in sufficient detail that a recipient will
+ know whom to contact. If Contributor obtains such knowledge after
+ the Modification is made available as described in Section 3.2,
+ Contributor shall promptly modify the LEGAL file in all copies
+ Contributor makes available thereafter and shall take other steps
+ (such as notifying appropriate mailing lists or newsgroups)
+ reasonably calculated to inform those who received the Covered
+ Code that new knowledge has been obtained.
+
+ (b) Contributor APIs.
+ If Contributor's Modifications include an application programming
+ interface and Contributor has knowledge of patent licenses which
+ are reasonably necessary to implement that API, Contributor must
+ also include this information in the LEGAL file.
+
+ (c) Representations.
+ Contributor represents that, except as disclosed pursuant to
+ Section 3.4(a) above, Contributor believes that Contributor's
+ Modifications are Contributor's original creation(s) and/or
+ Contributor has sufficient rights to grant the rights conveyed by
+ this License.
+
+ 3.5. Required Notices.
+ You must duplicate the notice in Exhibit A in each file of the Source
+ Code. If it is not possible to put such notice in a particular Source
+ Code file due to its structure, then You must include such notice in a
+ location (such as a relevant directory) where a user would be likely
+ to look for such a notice. If You created one or more Modification(s)
+ You may add your name as a Contributor to the notice described in
+ Exhibit A. You must also duplicate this License in any documentation
+ for the Source Code where You describe recipients' rights or ownership
+ rights relating to Covered Code. You may choose to offer, and to
+ charge a fee for, warranty, support, indemnity or liability
+ obligations to one or more recipients of Covered Code. However, You
+ may do so only on Your own behalf, and not on behalf of the Initial
+ Developer or any Contributor. You must make it absolutely clear than
+ any such warranty, support, indemnity or liability obligation is
+ offered by You alone, and You hereby agree to indemnify the Initial
+ Developer and every Contributor for any liability incurred by the
+ Initial Developer or such Contributor as a result of warranty,
+ support, indemnity or liability terms You offer.
+
+ 3.6. Distribution of Executable Versions.
+ You may distribute Covered Code in Executable form only if the
+ requirements of Section 3.1-3.5 have been met for that Covered Code,
+ and if You include a notice stating that the Source Code version of
+ the Covered Code is available under the terms of this License,
+ including a description of how and where You have fulfilled the
+ obligations of Section 3.2. The notice must be conspicuously included
+ in any notice in an Executable version, related documentation or
+ collateral in which You describe recipients' rights relating to the
+ Covered Code. You may distribute the Executable version of Covered
+ Code or ownership rights under a license of Your choice, which may
+ contain terms different from this License, provided that You are in
+ compliance with the terms of this License and that the license for the
+ Executable version does not attempt to limit or alter the recipient's
+ rights in the Source Code version from the rights set forth in this
+ License. If You distribute the Executable version under a different
+ license You must make it absolutely clear that any terms which differ
+ from this License are offered by You alone, not by the Initial
+ Developer or any Contributor. You hereby agree to indemnify the
+ Initial Developer and every Contributor for any liability incurred by
+ the Initial Developer or such Contributor as a result of any such
+ terms You offer.
+
+ 3.7. Larger Works.
+ You may create a Larger Work by combining Covered Code with other code
+ not governed by the terms of this License and distribute the Larger
+ Work as a single product. In such a case, You must make sure the
+ requirements of this License are fulfilled for the Covered Code.
+
+4. Inability to Comply Due to Statute or Regulation.
+
+ If it is impossible for You to comply with any of the terms of this
+ License with respect to some or all of the Covered Code due to
+ statute, judicial order, or regulation then You must: (a) comply with
+ the terms of this License to the maximum extent possible; and (b)
+ describe the limitations and the code they affect. Such description
+ must be included in the LEGAL file described in Section 3.4 and must
+ be included with all distributions of the Source Code. Except to the
+ extent prohibited by statute or regulation, such description must be
+ sufficiently detailed for a recipient of ordinary skill to be able to
+ understand it.
+
+5. Application of this License.
+
+ This License applies to code to which the Initial Developer has
+ attached the notice in Exhibit A and to related Covered Code.
+
+6. Versions of the License.
+
+ 6.1. New Versions.
+ Netscape Communications Corporation ("Netscape") may publish revised
+ and/or new versions of the License from time to time. Each version
+ will be given a distinguishing version number.
+
+ 6.2. Effect of New Versions.
+ Once Covered Code has been published under a particular version of the
+ License, You may always continue to use it under the terms of that
+ version. You may also choose to use such Covered Code under the terms
+ of any subsequent version of the License published by Netscape. No one
+ other than Netscape has the right to modify the terms applicable to
+ Covered Code created under this License.
+
+ 6.3. Derivative Works.
+ If You create or use a modified version of this License (which you may
+ only do in order to apply it to code which is not already Covered Code
+ governed by this License), You must (a) rename Your license so that
+ the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape",
+ "MPL", "NPL" or any confusingly similar phrase do not appear in your
+ license (except to note that your license differs from this License)
+ and (b) otherwise make it clear that Your version of the license
+ contains terms which differ from the Mozilla Public License and
+ Netscape Public License. (Filling in the name of the Initial
+ Developer, Original Code or Contributor in the notice described in
+ Exhibit A shall not of themselves be deemed to be modifications of
+ this License.)
+
+7. DISCLAIMER OF WARRANTY.
+
+ COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS,
+ WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
+ WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF
+ DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING.
+ THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE
+ IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT,
+ YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE
+ COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER
+ OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF
+ ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.
+
+8. TERMINATION.
+
+ 8.1. This License and the rights granted hereunder will terminate
+ automatically if You fail to comply with terms herein and fail to cure
+ such breach within 30 days of becoming aware of the breach. All
+ sublicenses to the Covered Code which are properly granted shall
+ survive any termination of this License. Provisions which, by their
+ nature, must remain in effect beyond the termination of this License
+ shall survive.
+
+ 8.2. If You initiate litigation by asserting a patent infringement
+ claim (excluding declatory judgment actions) against Initial Developer
+ or a Contributor (the Initial Developer or Contributor against whom
+ You file such action is referred to as "Participant") alleging that:
+
+ (a) such Participant's Contributor Version directly or indirectly
+ infringes any patent, then any and all rights granted by such
+ Participant to You under Sections 2.1 and/or 2.2 of this License
+ shall, upon 60 days notice from Participant terminate prospectively,
+ unless if within 60 days after receipt of notice You either: (i)
+ agree in writing to pay Participant a mutually agreeable reasonable
+ royalty for Your past and future use of Modifications made by such
+ Participant, or (ii) withdraw Your litigation claim with respect to
+ the Contributor Version against such Participant. If within 60 days
+ of notice, a reasonable royalty and payment arrangement are not
+ mutually agreed upon in writing by the parties or the litigation claim
+ is not withdrawn, the rights granted by Participant to You under
+ Sections 2.1 and/or 2.2 automatically terminate at the expiration of
+ the 60 day notice period specified above.
+
+ (b) any software, hardware, or device, other than such Participant's
+ Contributor Version, directly or indirectly infringes any patent, then
+ any rights granted to You by such Participant under Sections 2.1(b)
+ and 2.2(b) are revoked effective as of the date You first made, used,
+ sold, distributed, or had made, Modifications made by that
+ Participant.
+
+ 8.3. If You assert a patent infringement claim against Participant
+ alleging that such Participant's Contributor Version directly or
+ indirectly infringes any patent where such claim is resolved (such as
+ by license or settlement) prior to the initiation of patent
+ infringement litigation, then the reasonable value of the licenses
+ granted by such Participant under Sections 2.1 or 2.2 shall be taken
+ into account in determining the amount or value of any payment or
+ license.
+
+ 8.4. In the event of termination under Sections 8.1 or 8.2 above,
+ all end user license agreements (excluding distributors and resellers)
+ which have been validly granted by You or any distributor hereunder
+ prior to termination shall survive termination.
+
+9. LIMITATION OF LIABILITY.
+
+ UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT
+ (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL
+ DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE,
+ OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR
+ ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY
+ CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL,
+ WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER
+ COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN
+ INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF
+ LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY
+ RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW
+ PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE
+ EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO
+ THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU.
+
+10. U.S. GOVERNMENT END USERS.
+
+ The Covered Code is a "commercial item," as that term is defined in
+ 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer
+ software" and "commercial computer software documentation," as such
+ terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48
+ C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995),
+ all U.S. Government End Users acquire Covered Code with only those
+ rights set forth herein.
+
+11. MISCELLANEOUS.
+
+ This License represents the complete agreement concerning subject
+ matter hereof. If any provision of this License is held to be
+ unenforceable, such provision shall be reformed only to the extent
+ necessary to make it enforceable. This License shall be governed by
+ California law provisions (except to the extent applicable law, if
+ any, provides otherwise), excluding its conflict-of-law provisions.
+ With respect to disputes in which at least one party is a citizen of,
+ or an entity chartered or registered to do business in the United
+ States of America, any litigation relating to this License shall be
+ subject to the jurisdiction of the Federal Courts of the Northern
+ District of California, with venue lying in Santa Clara County,
+ California, with the losing party responsible for costs, including
+ without limitation, court costs and reasonable attorneys' fees and
+ expenses. The application of the United Nations Convention on
+ Contracts for the International Sale of Goods is expressly excluded.
+ Any law or regulation which provides that the language of a contract
+ shall be construed against the drafter shall not apply to this
+ License.
+
+12. RESPONSIBILITY FOR CLAIMS.
+
+ As between Initial Developer and the Contributors, each party is
+ responsible for claims and damages arising, directly or indirectly,
+ out of its utilization of rights under this License and You agree to
+ work with Initial Developer and Contributors to distribute such
+ responsibility on an equitable basis. Nothing herein is intended or
+ shall be deemed to constitute any admission of liability.
+
+13. MULTIPLE-LICENSED CODE.
+
+ Initial Developer may designate portions of the Covered Code as
+ "Multiple-Licensed". "Multiple-Licensed" means that the Initial
+ Developer permits you to utilize portions of the Covered Code under
+ Your choice of the MPL or the alternative licenses, if any, specified
+ by the Initial Developer in the file described in Exhibit A.
+
+EXHIBIT A -Mozilla Public License.
+
+ ``The contents of this file are subject to the Mozilla Public License
+ Version 1.1 (the "License"); you may not use this file except in
+ compliance with the License. You may obtain a copy of the License at
+ https://www.mozilla.org/MPL/
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+ License for the specific language governing rights and limitations
+ under the License.
+
+ The Original Code is ______________________________________.
+
+ The Initial Developer of the Original Code is ________________________.
+ Portions created by ______________________ are Copyright (C) ______
+ _______________________. All Rights Reserved.
+
+ Contributor(s): ______________________________________.
+
+ Alternatively, the contents of this file may be used under the terms
+ of the _____ license (the "[___] License"), in which case the
+ provisions of [______] License are applicable instead of those
+ above. If you wish to allow use of your version of this file only
+ under the terms of the [____] License and not to allow others to use
+ your version of this file under the MPL, indicate your decision by
+ deleting the provisions above and replace them with the notice and
+ other provisions required by the [___] License. If you do not delete
+ the provisions above, a recipient may use your version of this file
+ under either the MPL or the [___] License."
+
+ [NOTE: The text of this Exhibit A may differ slightly from the text of
+ the notices in the Source Code files of the Original Code. You should
+ use the text of this Exhibit A rather than the text found in the
+ Original Code Source Code for Your Modifications.]
diff --git a/maven-core/pom.xml b/maven-core/pom.xml
index 1f52e1b63bad..f9d19d4eb865 100644
--- a/maven-core/pom.xml
+++ b/maven-core/pom.xml
@@ -141,6 +141,32 @@ under the License.
hamcrest-librarytest
+
+ org.apache.httpcomponents
+ httpclient
+ 4.5.6
+
+
+ net.openhft
+ zero-allocation-hashing
+ 0.9
+
+
+ jakarta.xml.bind
+ jakarta.xml.bind-api
+ 2.3.3
+
+
+ com.sun.xml.bind
+ jaxb-impl
+ 2.3.3
+ runtime
+
+
+ com.github.albfernandez
+ juniversalchardet
+ 2.4.0
+
@@ -161,6 +187,7 @@ under the License.
plugin-manager.txtproject-builder.txtsrc/site/resources/design/**
+ src/main/resources/cache-*
@@ -252,6 +279,26 @@ under the License.
+
+ org.codehaus.mojo
+ jaxb2-maven-plugin
+ 2.5.0
+
+
+ xjc
+
+ xjc
+
+
+
+
+ org.apache.maven.caching.jaxb
+
+
+
+
+
+
diff --git a/maven-core/src/main/java/org/apache/maven/caching/ArtifactsRepository.java b/maven-core/src/main/java/org/apache/maven/caching/ArtifactsRepository.java
new file mode 100644
index 000000000000..65315498c7ff
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/ArtifactsRepository.java
@@ -0,0 +1,42 @@
+package org.apache.maven.caching;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.caching.jaxb.CacheReportType;
+import org.apache.maven.caching.xml.BuildInfo;
+import org.apache.maven.execution.MavenSession;
+
+import java.io.IOException;
+
+/**
+ * ArtifactsRepository
+ */
+public interface ArtifactsRepository
+{
+
+ BuildInfo findBuild( CacheContext context ) throws IOException;
+
+ void saveBuildInfo( CacheResult cacheResult, BuildInfo buildInfo ) throws IOException;
+
+ void saveArtifactFile( CacheResult cacheResult, Artifact artifact ) throws IOException;
+
+ void saveCacheReport( String buildId, MavenSession session, CacheReportType cacheReport ) throws IOException;
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/CacheContext.java b/maven-core/src/main/java/org/apache/maven/caching/CacheContext.java
new file mode 100644
index 000000000000..569286d8b841
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/CacheContext.java
@@ -0,0 +1,58 @@
+package org.apache.maven.caching;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.caching.jaxb.ProjectsInputInfoType;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.project.MavenProject;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * CacheContext
+ */
+public class CacheContext
+{
+ private final MavenProject project;
+ private final ProjectsInputInfoType inputInfo;
+ private final MavenSession session;
+
+ public CacheContext( MavenProject project, ProjectsInputInfoType inputInfo, MavenSession session )
+ {
+ this.project = checkNotNull( project );
+ this.inputInfo = checkNotNull( inputInfo );
+ this.session = checkNotNull( session );
+ }
+
+ public MavenProject getProject()
+ {
+ return project;
+ }
+
+ public ProjectsInputInfoType getInputInfo()
+ {
+ return inputInfo;
+ }
+
+ public MavenSession getSession()
+ {
+ return session;
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/CacheController.java b/maven-core/src/main/java/org/apache/maven/caching/CacheController.java
new file mode 100644
index 000000000000..a91c284e2414
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/CacheController.java
@@ -0,0 +1,52 @@
+package org.apache.maven.caching;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.execution.MojoExecutionEvent;
+import org.apache.maven.lifecycle.internal.ProjectIndex;
+import org.apache.maven.plugin.MojoExecution;
+import org.apache.maven.project.MavenProject;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * CacheController
+ */
+public interface CacheController
+{
+
+ CacheResult findCachedBuild( MavenSession session,
+ MavenProject project,
+ ProjectIndex projectIndex,
+ List mojoExecutions );
+
+ boolean restoreProjectArtifacts( CacheResult cacheResult );
+
+ void save( CacheResult cacheResult,
+ List mojoExecutions,
+ Map executionEvents );
+
+ boolean isForcedExecution( MavenProject project, MojoExecution execution );
+
+ void saveCacheReport( MavenSession session );
+
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/CacheControllerImpl.java b/maven-core/src/main/java/org/apache/maven/caching/CacheControllerImpl.java
new file mode 100644
index 000000000000..ad9d490c6ebb
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/CacheControllerImpl.java
@@ -0,0 +1,942 @@
+package org.apache.maven.caching;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import com.google.common.base.Optional;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.mutable.MutableBoolean;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.handler.ArtifactHandler;
+import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
+import org.apache.maven.caching.checksum.KeyUtils;
+import org.apache.maven.caching.checksum.MavenProjectInput;
+import org.apache.maven.caching.hash.HashAlgorithm;
+import org.apache.maven.caching.hash.HashFactory;
+import org.apache.maven.caching.jaxb.ArtifactType;
+import org.apache.maven.caching.jaxb.BuildDiffType;
+import org.apache.maven.caching.jaxb.BuildInfoType;
+import org.apache.maven.caching.jaxb.CacheReportType;
+import org.apache.maven.caching.jaxb.CompletedExecutionType;
+import org.apache.maven.caching.jaxb.DigestItemType;
+import org.apache.maven.caching.jaxb.ProjectReportType;
+import org.apache.maven.caching.jaxb.ProjectsInputInfoType;
+import org.apache.maven.caching.jaxb.PropertyNameType;
+import org.apache.maven.caching.jaxb.TrackedPropertyType;
+import org.apache.maven.caching.xml.BuildInfo;
+import org.apache.maven.caching.xml.CacheConfig;
+import org.apache.maven.caching.xml.CacheSource;
+import org.apache.maven.caching.xml.DtoUtils;
+import org.apache.maven.caching.xml.XmlService;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.execution.MojoExecutionEvent;
+import org.apache.maven.lifecycle.internal.ProjectIndex;
+import org.apache.maven.plugin.MavenPluginManager;
+import org.apache.maven.plugin.Mojo;
+import org.apache.maven.plugin.MojoExecution;
+import org.apache.maven.plugin.descriptor.Parameter;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.project.MavenProjectHelper;
+import org.apache.maven.repository.RepositorySystem;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.logging.Logger;
+import org.codehaus.plexus.util.ReflectionUtils;
+
+import javax.annotation.Nonnull;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.regex.Pattern;
+
+import static java.nio.file.StandardOpenOption.CREATE;
+import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
+import static org.apache.commons.lang3.StringUtils.replace;
+import static org.apache.commons.lang3.StringUtils.split;
+import static org.apache.maven.caching.CacheResult.empty;
+import static org.apache.maven.caching.CacheResult.failure;
+import static org.apache.maven.caching.CacheResult.partialSuccess;
+import static org.apache.maven.caching.CacheResult.rebuilded;
+import static org.apache.maven.caching.CacheResult.success;
+import static org.apache.maven.caching.HttpRepositoryImpl.BUILDINFO_XML;
+import static org.apache.maven.caching.checksum.KeyUtils.getVersionlessProjectKey;
+import static org.apache.maven.caching.checksum.MavenProjectInput.CACHE_IMPLMENTATION_VERSION;
+
+/**
+ * CacheControllerImpl
+ */
+@Component( role = CacheController.class )
+public class CacheControllerImpl implements CacheController
+{
+
+ public static final String FILE_SEPARATOR_SUBST = "_";
+ private static final String GENERATEDSOURCES = "generatedsources";
+ private static final String GENERATEDSOURCES_PREFIX = GENERATEDSOURCES + FILE_SEPARATOR_SUBST;
+ @Requirement
+ private Logger logger;
+
+ @Requirement
+ private MavenPluginManager mavenPluginManager;
+
+ @Requirement
+ private MavenProjectHelper projectHelper;
+
+ @Requirement
+ private LocalArtifactsRepository localCache;
+
+ @Requirement
+ private RemoteArtifactsRepository remoteCache;
+
+ @Requirement
+ private CacheConfig cacheConfig;
+
+ @Requirement
+ private RepositorySystem repoSystem;
+
+ @Requirement
+ private ArtifactHandlerManager artifactHandlerManager;
+
+ @Requirement
+ private XmlService xmlService;
+
+ private final ConcurrentMap artifactDigestByKey = new ConcurrentHashMap<>();
+
+ private final ConcurrentMap cacheResults = new ConcurrentHashMap<>();
+
+ private volatile BuildInfoType.Scm scm;
+
+ @Override
+ @Nonnull
+ public CacheResult findCachedBuild( MavenSession session, MavenProject project, ProjectIndex projectIndex,
+ List mojoExecutions )
+ {
+
+ final String highestRequestPhase = Iterables.getLast( mojoExecutions ).getLifecyclePhase();
+ if ( !ProjectUtils.isLaterPhase( highestRequestPhase, "post-clean" ) )
+ {
+ return empty();
+ }
+
+ logInfo( project, "Attempting to restore project from build cache" );
+
+ ProjectsInputInfoType inputInfo = calculateInput( project, session, projectIndex );
+
+ final CacheContext context = new CacheContext( project, inputInfo, session );
+ // remote build first
+ CacheResult result = findCachedBuild( mojoExecutions, context );
+
+ if ( !result.isSuccess() && result.getContext() != null )
+ {
+
+ logDebug( project, "Remote cache is incomplete or missing, trying local build" );
+
+ CacheResult localBuild = findLocalBuild( mojoExecutions, context );
+
+ if ( localBuild.isSuccess() || ( localBuild.isPartialSuccess() && !result.isPartialSuccess() ) )
+ {
+ result = localBuild;
+ }
+ }
+ cacheResults.put( getVersionlessProjectKey( project ), result );
+
+ return result;
+ }
+
+ private CacheResult findCachedBuild( List mojoExecutions, CacheContext context )
+ {
+ BuildInfo cachedBuild = null;
+ try
+ {
+ cachedBuild = localCache.findBuild( context );
+ return analyzeResult( context, mojoExecutions, cachedBuild );
+ }
+ catch ( Exception e )
+ {
+ logError( context.getProject(), "Cannot read cached build", e );
+ return cachedBuild != null ? failure( cachedBuild, context ) : failure( context );
+ }
+ }
+
+ private CacheResult findLocalBuild( List mojoExecutions, CacheContext context )
+ {
+ BuildInfo localBuild = null;
+ try
+ {
+ localBuild = localCache.findLocalBuild( context );
+ return analyzeResult( context, mojoExecutions, localBuild );
+ }
+ catch ( Exception e )
+ {
+ logError( context.getProject(), "Cannot read local build", e );
+ return localBuild != null ? failure( localBuild, context ) : failure( context );
+ }
+ }
+
+ private CacheResult analyzeResult( CacheContext context, List mojoExecutions, BuildInfo info )
+ {
+
+ try
+ {
+ if ( info != null )
+ {
+
+ final MavenProject project = context.getProject();
+ final ProjectsInputInfoType inputInfo = context.getInputInfo();
+
+ logInfo( project, "Found cached build, restoring from cache " + inputInfo.getChecksum() );
+
+ if ( logger.isDebugEnabled() )
+ {
+ logDebug( project, "Cached build details: " + info.toString() );
+ }
+
+ final String cacheImplementationVersion = info.getCacheImplementationVersion();
+ if ( !CACHE_IMPLMENTATION_VERSION.equals( cacheImplementationVersion ) )
+ {
+ logger.warn(
+ "Maven and cached build implementations mismatch, caching might not work correctly. "
+ + "Implementation version: " + CACHE_IMPLMENTATION_VERSION + ", cached build: "
+ + info.getCacheImplementationVersion() );
+ }
+
+ final List cachedSegment = info.getCachedSegment( mojoExecutions );
+
+ if ( !info.isAllExecutionsPresent( cachedSegment, logger ) )
+ {
+ logInfo( project, "Cached build doesn't contains all requested plugin executions, cannot restore" );
+ return failure( info, context );
+ }
+
+ if ( !isCachedSegmentPropertiesPresent( project, info, cachedSegment ) )
+ {
+ logInfo( project, "Cached build violates cache rules, cannot restore" );
+ return failure( info, context );
+ }
+
+ final String highestRequestPhase = Iterables.getLast( mojoExecutions ).getLifecyclePhase();
+ final String highestCompletedGoal = info.getHighestCompletedGoal();
+ if ( ProjectUtils.isLaterPhase( highestRequestPhase, highestCompletedGoal ) && !canIgnoreMissingSegment(
+ info, mojoExecutions ) )
+ {
+ logInfo( project,
+ "Project restored partially. Highest cached goal: " + highestCompletedGoal + ", requested: "
+ + highestRequestPhase );
+ return partialSuccess( info, context );
+ }
+
+ return success( info, context );
+ }
+ else
+ {
+ logInfo( context.getProject(), "Project is not found in cache" );
+ return empty( context );
+ }
+ }
+ catch ( Exception e )
+ {
+ logger.error( "Failed to restore project", e );
+ localCache.clearCache( context );
+ return empty( context );
+ }
+ }
+
+ private boolean canIgnoreMissingSegment( BuildInfo info, List mojoExecutions )
+ {
+ final List postCachedSegment = info.getPostCachedSegment( mojoExecutions );
+ for ( MojoExecution mojoExecution : postCachedSegment )
+ {
+ if ( !cacheConfig.canIgnore( mojoExecution ) )
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean restoreProjectArtifacts( CacheResult cacheResult )
+ {
+
+ final BuildInfo buildInfo = cacheResult.getBuildInfo();
+ final CacheContext context = cacheResult.getContext();
+ final MavenProject project = context.getProject();
+
+ final ArtifactType artifact = buildInfo.getArtifact();
+ artifact.setVersion( project.getVersion() );
+
+ try
+ {
+ if ( isNotBlank( artifact.getFileName() ) )
+ {
+ // TODO if remote is forced, probably need to refresh or reconcile all files
+ final Path artifactFile = localCache.getArtifactFile( context, cacheResult.getSource(), artifact );
+ if ( !Files.exists( artifactFile ) )
+ {
+ logInfo( project, "Missing file for cached build, cannot restore. File: " + artifactFile );
+ return false;
+ }
+ logDebug( project, "Setting project artifact " + artifact.getArtifactId() + " from: " + artifactFile );
+ project.getArtifact().setFile( artifactFile.toFile() );
+ project.getArtifact().setResolved( true );
+ putChecksum( artifact, context.getInputInfo().getChecksum() );
+ }
+
+ for ( ArtifactType attachedArtifact : buildInfo.getAttachedArtifacts() )
+ {
+ attachedArtifact.setVersion( project.getVersion() );
+ if ( isNotBlank( attachedArtifact.getFileName() ) )
+ {
+ final Path attachedArtifactFile = localCache.getArtifactFile( context, cacheResult.getSource(),
+ attachedArtifact );
+ if ( !Files.exists( attachedArtifactFile ) )
+ {
+ logInfo( project,
+ "Missing file for cached build, cannot restore project. File: "
+ + attachedArtifactFile );
+ project.getArtifact().setFile( null );
+ project.getArtifact().setResolved( false );
+ project.getAttachedArtifacts().clear();
+ return false;
+ }
+ logDebug( project,
+ "Attaching artifact " + artifact.getArtifactId() + " from: " + attachedArtifactFile );
+ if ( StringUtils.startsWith( attachedArtifact.getClassifier(), GENERATEDSOURCES_PREFIX ) )
+ {
+ // generated sources artifact
+ restoreGeneratedSources( attachedArtifact, attachedArtifactFile, project );
+ }
+ else
+ {
+ projectHelper.attachArtifact( project, attachedArtifact.getType(),
+ attachedArtifact.getClassifier(), attachedArtifactFile.toFile() );
+ }
+ putChecksum( attachedArtifact, context.getInputInfo().getChecksum() );
+ }
+ }
+ }
+ catch ( Exception e )
+ {
+ project.getArtifact().setFile( null );
+ project.getArtifact().setResolved( false );
+ project.getAttachedArtifacts().clear();
+ logError( project, "Cannot restore cache, continuing with normal build.", e );
+ return false;
+ }
+
+ return true;
+ }
+
+ private void putChecksum( ArtifactType artifact, String projectChecksum )
+ {
+
+ final DigestItemType projectArtifact = DtoUtils.createdDigestedByProjectChecksum( artifact, projectChecksum );
+ final String dependencyKey = KeyUtils.getArtifactKey( artifact );
+ artifactDigestByKey.put( dependencyKey, projectArtifact );
+
+ if ( !"pom".equals( artifact.getType() ) )
+ {
+ final ArtifactHandler artifactHandler = artifactHandlerManager.getArtifactHandler( artifact.getType() );
+ ArtifactType copy = DtoUtils.copy( artifact );
+ copy.setType( artifactHandler.getPackaging() );
+ artifactDigestByKey.put( KeyUtils.getArtifactKey( copy ), projectArtifact );
+ copy.setType( artifactHandler.getExtension() );
+ artifactDigestByKey.put( KeyUtils.getArtifactKey( copy ), projectArtifact );
+ }
+ }
+
+ private ProjectsInputInfoType calculateInput( MavenProject project, MavenSession session,
+ ProjectIndex projectIndex )
+ {
+ try
+ {
+ final MavenProjectInput inputs = new MavenProjectInput( project, session, cacheConfig, projectIndex,
+ artifactDigestByKey, repoSystem, artifactHandlerManager, logger, localCache, remoteCache );
+ return inputs.calculateChecksum( cacheConfig.getHashFactory() );
+ }
+ catch ( Exception e )
+ {
+ throw new RuntimeException( "Failed to calculate checksums for " + project.getArtifactId(), e );
+ }
+ }
+
+ @Override
+ public void save( CacheResult cacheResult, List mojoExecutions,
+ Map executionEvents )
+ {
+
+ CacheContext context = cacheResult.getContext();
+
+ if ( context == null || context.getInputInfo() == null )
+ {
+ logger.info( "Cannot save project in cache, skipping" );
+ return;
+ }
+
+ final MavenProject project = context.getProject();
+ final MavenSession session = context.getSession();
+ try
+ {
+
+ attachGeneratedSources( project );
+ attachOutputs( project );
+
+ final Artifact projectArtifact = project.getArtifact();
+ final HashFactory hashFactory = cacheConfig.getHashFactory();
+ final HashAlgorithm algorithm = hashFactory.createAlgorithm();
+ final ArtifactType projectArtifactDto = artifactDto( project.getArtifact(), algorithm );
+
+ final List attachedArtifacts =
+ project.getAttachedArtifacts() != null ? project.getAttachedArtifacts() : Collections.emptyList();
+ List attachedArtifactDtos = artifactDtos( attachedArtifacts, algorithm );
+
+ List completedExecution = buildExecutionInfo( mojoExecutions, executionEvents );
+
+ final BuildInfo buildInfo = new BuildInfo( session.getGoals(), projectArtifactDto, attachedArtifactDtos,
+ context.getInputInfo(), completedExecution, hashFactory.getAlgorithm() );
+ populateGitInfo( buildInfo, session );
+ buildInfo.getDto().setFinal( cacheConfig.isSaveFinal() );
+ cacheResults.put( getVersionlessProjectKey( project ), rebuilded( cacheResult, buildInfo ) );
+
+ // if package phase presence means new artifacts were packaged
+ if ( project.hasLifecyclePhase( "package" ) )
+ {
+ localCache.beforeSave( context );
+ localCache.saveBuildInfo( cacheResult, buildInfo );
+ if ( projectArtifact.getFile() != null )
+ {
+ localCache.saveArtifactFile( cacheResult, projectArtifact );
+ putChecksum( projectArtifactDto, context.getInputInfo().getChecksum() );
+ }
+ for ( Artifact attachedArtifact : attachedArtifacts )
+ {
+ if ( attachedArtifact.getFile() != null && isOutputArtifact(
+ attachedArtifact.getFile().getName() ) )
+ {
+ localCache.saveArtifactFile( cacheResult, attachedArtifact );
+ }
+ }
+ for ( ArtifactType attachedArtifactDto : attachedArtifactDtos )
+ {
+ putChecksum( attachedArtifactDto, context.getInputInfo().getChecksum() );
+ }
+ }
+ else
+ {
+ localCache.saveBuildInfo( cacheResult, buildInfo );
+ }
+
+ if ( cacheConfig.isBaselineDiffEnabled() )
+ {
+ produceDiffReport( cacheResult, buildInfo );
+ }
+
+ }
+ catch ( Exception e )
+ {
+ try
+ {
+ logger.error( "Failed to save project, cleaning cache. Project: " + project, e );
+ localCache.clearCache( context );
+ }
+ catch ( Exception ex )
+ {
+ logger.error( "Failed to clean cache due to unexpected error:", e );
+ }
+ }
+ }
+
+ public void produceDiffReport( CacheResult cacheResult, BuildInfo buildInfo )
+ {
+ MavenProject project = cacheResult.getContext().getProject();
+ Optional baselineHolder = remoteCache.findBaselineBuild( project );
+ if ( baselineHolder.isPresent() )
+ {
+ BuildInfo baseline = baselineHolder.get();
+ String outputDirectory = project.getBuild().getDirectory();
+ Path reportOutputDir = Paths.get( outputDirectory, "incremental-maven" );
+ logInfo( project, "Saving cache builds diff to: " + reportOutputDir );
+ BuildDiffType diff = new CacheDiff( buildInfo.getDto(), baseline.getDto(), cacheConfig ).compare();
+ try
+ {
+ Files.createDirectories( reportOutputDir );
+ final ProjectsInputInfoType baselineInputs = baseline.getDto().getProjectsInputInfo();
+ final String checksum = baselineInputs.getChecksum();
+ Files.write( reportOutputDir.resolve( "buildinfo-baseline-" + checksum + ".xml" ),
+ xmlService.toBytes( baseline.getDto() ), TRUNCATE_EXISTING, CREATE );
+ Files.write( reportOutputDir.resolve( "buildinfo-" + checksum + ".xml" ),
+ xmlService.toBytes( buildInfo.getDto() ), TRUNCATE_EXISTING, CREATE );
+ Files.write( reportOutputDir.resolve( "buildsdiff-" + checksum + ".xml" ),
+ xmlService.toBytes( diff ), TRUNCATE_EXISTING, CREATE );
+ final Optional pom = CacheDiff.findPom( buildInfo.getDto().getProjectsInputInfo() );
+ if ( pom.isPresent() )
+ {
+ Files.write( reportOutputDir.resolve( "effective-pom-" + checksum + ".xml" ),
+ pom.get().getValue().getBytes( StandardCharsets.UTF_8 ),
+ TRUNCATE_EXISTING, CREATE );
+ }
+ final Optional baselinePom = CacheDiff.findPom( baselineInputs );
+ if ( baselinePom.isPresent() )
+ {
+ Files.write( reportOutputDir.resolve(
+ "effective-pom-baseline-" + baselineInputs.getChecksum() + ".xml" ),
+ baselinePom.get().getValue().getBytes( StandardCharsets.UTF_8 ),
+ TRUNCATE_EXISTING, CREATE );
+ }
+ }
+ catch ( IOException e )
+ {
+ logError( project, "Cannot produce build diff for project", e );
+ }
+ }
+ else
+ {
+ logInfo( project, "Cannot find project in baseline build, skipping diff" );
+ }
+ }
+
+ private List artifactDtos( List attachedArtifacts, HashAlgorithm digest ) throws IOException
+ {
+ List result = new ArrayList<>();
+ for ( Artifact attachedArtifact : attachedArtifacts )
+ {
+ if ( attachedArtifact.getFile() != null && isOutputArtifact( attachedArtifact.getFile().getName() ) )
+ {
+ result.add( artifactDto( attachedArtifact, digest ) );
+ }
+ }
+ return result;
+ }
+
+ private ArtifactType artifactDto( Artifact projectArtifact, HashAlgorithm algorithm ) throws IOException
+ {
+ final ArtifactType dto = DtoUtils.createDto( projectArtifact );
+ if ( projectArtifact.getFile() != null && projectArtifact.getFile().isFile() )
+ {
+ final Path file = projectArtifact.getFile().toPath();
+ dto.setFileHash( algorithm.hash( file ) );
+ dto.setFileSize( BigInteger.valueOf( Files.size( file ) ) );
+ }
+ return dto;
+ }
+
+ private List buildExecutionInfo( List mojoExecutions,
+ Map executionEvents )
+ {
+ List list = new ArrayList<>();
+ for ( MojoExecution mojoExecution : mojoExecutions )
+ {
+ final String executionKey = ProjectUtils.mojoExecutionKey( mojoExecution );
+ final MojoExecutionEvent executionEvent = executionEvents.get( executionKey );
+ CompletedExecutionType executionInfo = new CompletedExecutionType();
+ executionInfo.setExecutionKey( executionKey );
+ executionInfo.setMojoClassName( mojoExecution.getMojoDescriptor().getImplementation() );
+ if ( executionEvent != null )
+ {
+ recordMojoProperties( executionInfo, executionEvent );
+ }
+ list.add( executionInfo );
+ }
+ return list;
+ }
+
+ private void recordMojoProperties( CompletedExecutionType execution, MojoExecutionEvent executionEvent )
+ {
+ final MojoExecution mojoExecution = executionEvent.getExecution();
+
+ final boolean logAll = cacheConfig.isLogAllProperties( mojoExecution );
+ List trackedProperties = cacheConfig.getTrackedProperties( mojoExecution );
+ List noLogProperties = cacheConfig.getNologProperties( mojoExecution );
+ List forceLogProperties = cacheConfig.getLoggedProperties( mojoExecution );
+ final Mojo mojo = executionEvent.getMojo();
+
+ final File baseDir = executionEvent.getProject().getBasedir();
+ final String baseDirPath = FilenameUtils.normalizeNoEndSeparator( baseDir.getAbsolutePath() ) + File.separator;
+
+ final List parameters = mojoExecution.getMojoDescriptor().getParameters();
+ for ( Parameter parameter : parameters )
+ {
+ // editable parameters could be configured by user
+ if ( !parameter.isEditable() )
+ {
+ continue;
+ }
+
+ final String propertyName = parameter.getName();
+ final boolean tracked = isTracked( propertyName, trackedProperties );
+ if ( !tracked && isExcluded( propertyName, logAll, noLogProperties, forceLogProperties ) )
+ {
+ continue;
+ }
+
+ try
+ {
+ final Object value = ReflectionUtils.getValueIncludingSuperclasses( propertyName, mojo );
+ DtoUtils.addProperty( execution, propertyName, value, baseDirPath, tracked );
+ }
+ catch ( IllegalAccessException e )
+ {
+ logInfo( executionEvent.getProject(),
+ "Cannot get property " + propertyName + " value from " + mojo + ": " + e.getMessage() );
+ if ( tracked )
+ {
+ throw new IllegalArgumentException(
+ "Property configured in cache introspection config for " + mojo + " is not accessible: "
+ + propertyName );
+ }
+ }
+ }
+ }
+
+ private boolean isExcluded( String propertyName, boolean logAll, List excludedProperties,
+ List forceLogProperties )
+ {
+
+ if ( !forceLogProperties.isEmpty() )
+ {
+ for ( PropertyNameType logProperty : forceLogProperties )
+ {
+ if ( StringUtils.equals( propertyName, logProperty.getPropertyName() ) )
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ if ( !excludedProperties.isEmpty() )
+ {
+ for ( PropertyNameType excludedProperty : excludedProperties )
+ {
+ if ( StringUtils.equals( propertyName, excludedProperty.getPropertyName() ) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ return !logAll;
+ }
+
+ private boolean isTracked( String propertyName, List trackedProperties )
+ {
+ for ( TrackedPropertyType trackedProperty : trackedProperties )
+ {
+ if ( StringUtils.equals( propertyName, trackedProperty.getPropertyName() ) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isCachedSegmentPropertiesPresent( MavenProject project, BuildInfo buildInfo,
+ List mojoExecutions )
+ {
+ for ( MojoExecution mojoExecution : mojoExecutions )
+ {
+
+ // completion of all mojos checked above, so we expect tp have execution info here
+ final List trackedProperties = cacheConfig.getTrackedProperties( mojoExecution );
+ final CompletedExecutionType cachedExecution = buildInfo.findMojoExecutionInfo( mojoExecution );
+
+ if ( cachedExecution == null )
+ {
+ logInfo( project,
+ "Execution is not cached. Plugin: " + mojoExecution.getExecutionId() + ", goal"
+ + mojoExecution.getGoal() );
+ return false;
+ }
+
+ if ( !DtoUtils.containsAllProperties( cachedExecution, trackedProperties ) )
+ {
+ logInfo( project, "Build info doesn't match rules. Plugin: " + mojoExecution.getExecutionId() );
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void logDebug( MavenProject project, String message )
+ {
+ if ( logger.isDebugEnabled() )
+ {
+ logger.debug( "[CACHE][" + project.getArtifactId() + "] " + message );
+ }
+ }
+
+ private void logInfo( MavenProject project, String message )
+ {
+ logger.info( "[CACHE][" + project.getArtifactId() + "] " + message );
+ }
+
+ private void logError( MavenProject project, String message, Exception e )
+ {
+ logger.error( "[CACHE][" + project.getArtifactId() + "] " + message, e );
+ }
+
+ @Override
+ public boolean isForcedExecution( MavenProject project, MojoExecution execution )
+ {
+
+ if ( cacheConfig.isForcedExecution( execution ) )
+ {
+ return true;
+ }
+ final Properties properties = project.getProperties();
+ final String alwaysRunPlugins = properties.getProperty( "remote.cache.alwaysRunPlugins" );
+ ArrayList alwaysRunPluginsList = new ArrayList<>();
+ if ( alwaysRunPlugins != null )
+ {
+ alwaysRunPluginsList = Lists.newArrayList( split( alwaysRunPlugins, "," ) );
+ }
+ for ( String pluginAndGoal : alwaysRunPluginsList )
+ {
+ final String[] tokens = pluginAndGoal.split( ":" );
+ final String alwaysRunPlugin = tokens[0];
+ String alwaysRunGoal = tokens.length == 1 ? "*" : tokens[1];
+ if ( StringUtils.equals( execution.getPlugin().getArtifactId(), alwaysRunPlugin ) && ( "*".equals(
+ alwaysRunGoal ) || StringUtils.equals( execution.getGoal(), alwaysRunGoal ) ) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void saveCacheReport( MavenSession session )
+ {
+ try
+ {
+
+ CacheReportType cacheReport = new CacheReportType();
+ for ( CacheResult result : cacheResults.values() )
+ {
+ ProjectReportType projectReport = new ProjectReportType();
+ CacheContext context = result.getContext();
+ MavenProject project = context.getProject();
+ projectReport.setGroupId( project.getGroupId() );
+ projectReport.setArtifactId( project.getArtifactId() );
+ projectReport.setChecksum( context.getInputInfo().getChecksum() );
+ boolean checksumMatched = result.getStatus() != RestoreStatus.EMPTY;
+ projectReport.setChecksumMatched( checksumMatched );
+ projectReport.setLifecycleMatched( checksumMatched && result.isSuccess() );
+ projectReport.setSource( String.valueOf( result.getSource() ) );
+ if ( result.getSource() == CacheSource.REMOTE )
+ {
+ projectReport.setUrl( remoteCache.getResourceUrl( context, BUILDINFO_XML ) );
+ }
+ else if ( result.getSource() == CacheSource.BUILD && cacheConfig.isSaveToRemote() )
+ {
+ projectReport.setSharedToRemote( true );
+ projectReport.setUrl( remoteCache.getResourceUrl( context, BUILDINFO_XML ) );
+ }
+ cacheReport.getProject().add( projectReport );
+ }
+
+ String buildId = UUID.randomUUID().toString();
+ localCache.saveCacheReport( buildId, session, cacheReport );
+ }
+ catch ( Exception e )
+ {
+ logger.error( "Cannot save incremental build aggregated report", e );
+ }
+ }
+
+ private void populateGitInfo( BuildInfo buildInfo, MavenSession session ) throws IOException
+ {
+ if ( scm == null )
+ {
+ synchronized ( this )
+ {
+ if ( scm == null )
+ {
+ try
+ {
+ scm = ProjectUtils.readGitInfo( session );
+ }
+ catch ( IOException e )
+ {
+ scm = new BuildInfoType.Scm();
+ logger.error( "Cannot populate git info", e );
+ }
+ }
+ }
+ }
+ buildInfo.getDto().setScm( scm );
+ }
+
+ private void zipAndAttachArtifact( MavenProject project, Path dir, String classifier ) throws IOException
+ {
+
+ try ( final InputStream inputStream = ZipUtils.zipFolder( dir ) )
+ {
+ File tempFile = File.createTempFile( "maven-incremental", project.getArtifactId() );
+ tempFile.deleteOnExit();
+ Files.copy( inputStream, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING );
+ projectHelper.attachArtifact( project, "zip", classifier, tempFile );
+ }
+ }
+
+ private String pathToClassifier( Path relative )
+ {
+ final int nameCount = relative.getNameCount();
+ List segments = new ArrayList<>( nameCount + 1 );
+ for ( int i = 0; i < nameCount; i++ )
+ {
+ segments.add( relative.getName( i ).toFile().getName() );
+ }
+ // todo handle _ in file names
+ return GENERATEDSOURCES_PREFIX + StringUtils.join( segments.iterator(), FILE_SEPARATOR_SUBST );
+ }
+
+ private Path classifierToPath( Path outputDir, String classifier )
+ {
+ classifier = StringUtils.removeStart( classifier, GENERATEDSOURCES_PREFIX );
+ final String relPath = replace( classifier, FILE_SEPARATOR_SUBST, File.separator );
+ return outputDir.resolve( relPath );
+ }
+
+ private void restoreGeneratedSources( ArtifactType artifact, Path artifactFilePath, MavenProject project )
+ throws IOException
+ {
+ final Path targetDir = Paths.get( project.getBuild().getDirectory() );
+ final Path outputDir = classifierToPath( targetDir, artifact.getClassifier() );
+ try ( InputStream is = Files.newInputStream( artifactFilePath ) )
+ {
+ if ( Files.exists( outputDir ) )
+ {
+ FileUtils.cleanDirectory( outputDir.toFile() );
+ }
+ else
+ {
+ Files.createDirectories( outputDir );
+ }
+ ZipUtils.unzip( is, outputDir );
+ }
+ }
+
+ //TODO: move to config
+ public void attachGeneratedSources( MavenProject project ) throws IOException
+ {
+ final Path targetDir = Paths.get( project.getBuild().getDirectory() );
+
+ final Path generatedSourcesDir = targetDir.resolve( "generated-sources" );
+ attachDirIfNotEmpty( generatedSourcesDir, targetDir, project );
+
+ final Path generatedTestSourcesDir = targetDir.resolve( "generated-test-sources" );
+ attachDirIfNotEmpty( generatedTestSourcesDir, targetDir, project );
+
+ Set sourceRoots = new TreeSet<>();
+ if ( project.getCompileSourceRoots() != null )
+ {
+ sourceRoots.addAll( project.getCompileSourceRoots() );
+ }
+ if ( project.getTestCompileSourceRoots() != null )
+ {
+ sourceRoots.addAll( project.getTestCompileSourceRoots() );
+ }
+
+ for ( String sourceRoot : sourceRoots )
+ {
+ final Path sourceRootPath = Paths.get( sourceRoot );
+ if ( Files.isDirectory( sourceRootPath ) && sourceRootPath.startsWith(
+ targetDir ) && !( sourceRootPath.startsWith( generatedSourcesDir ) || sourceRootPath.startsWith(
+ generatedTestSourcesDir ) ) )
+ { // dir within target
+ attachDirIfNotEmpty( sourceRootPath, targetDir, project );
+ }
+ }
+ }
+
+ private void attachOutputs( MavenProject project ) throws IOException
+ {
+ final List attachedDirs = cacheConfig.getAttachedOutputs();
+ for ( String dir : attachedDirs )
+ {
+ final Path targetDir = Paths.get( project.getBuild().getDirectory() );
+ final Path outputDir = targetDir.resolve( dir );
+ attachDirIfNotEmpty( outputDir, targetDir, project );
+ }
+ }
+
+ private void attachDirIfNotEmpty( Path candidateSubDir, Path parentDir, MavenProject project ) throws IOException
+ {
+ if ( Files.isDirectory( candidateSubDir ) && hasFiles( candidateSubDir ) )
+ {
+ final Path relativePath = parentDir.relativize( candidateSubDir );
+ final String classifier = pathToClassifier( relativePath );
+ zipAndAttachArtifact( project, candidateSubDir, classifier );
+ logDebug( project, "Attached directory: " + candidateSubDir );
+ }
+ }
+
+ private boolean hasFiles( Path candidateSubDir ) throws IOException
+ {
+ final MutableBoolean hasFiles = new MutableBoolean();
+ Files.walkFileTree( candidateSubDir, new SimpleFileVisitor()
+ {
+ @Override
+ public FileVisitResult visitFile( Path path, BasicFileAttributes basicFileAttributes )
+ {
+ hasFiles.setTrue();
+ return FileVisitResult.TERMINATE;
+ }
+ } );
+ return hasFiles.booleanValue();
+ }
+
+ private boolean isOutputArtifact( String name )
+ {
+ List excludePatterns = cacheConfig.getExcludePatterns();
+ for ( Pattern pattern : excludePatterns )
+ {
+ if ( pattern.matcher( name ).matches() )
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/CacheDiff.java b/maven-core/src/main/java/org/apache/maven/caching/CacheDiff.java
new file mode 100644
index 000000000000..9e066a598eff
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/CacheDiff.java
@@ -0,0 +1,345 @@
+package org.apache.maven.caching;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import com.google.common.base.Optional;
+import com.google.common.collect.Sets;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.maven.caching.jaxb.BuildDiffType;
+import org.apache.maven.caching.jaxb.BuildInfoType;
+import org.apache.maven.caching.jaxb.CompletedExecutionType;
+import org.apache.maven.caching.jaxb.DigestItemType;
+import org.apache.maven.caching.jaxb.MismatchType;
+import org.apache.maven.caching.jaxb.ProjectsInputInfoType;
+import org.apache.maven.caching.jaxb.PropertyValueType;
+import org.apache.maven.caching.xml.CacheConfig;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Utility class for comparing 2 builds
+ */
+public class CacheDiff
+{
+
+ private final CacheConfig config;
+ private final BuildInfoType current;
+ private final BuildInfoType baseline;
+ private final LinkedList report;
+
+ public CacheDiff( BuildInfoType current, BuildInfoType baseline, CacheConfig config )
+ {
+ this.current = current;
+ this.baseline = baseline;
+ this.config = config;
+ this.report = new LinkedList<>();
+ }
+
+ public BuildDiffType compare()
+ {
+
+ if ( !StringUtils.equals( current.getHashFunction(), baseline.getHashFunction() ) )
+ {
+ addNewMismatch(
+ "hashFunction",
+ current.getHashFunction(),
+ baseline.getHashFunction(),
+ "Different algorithms render caches not comparable and cached could not be reused",
+ "Ensure the same algorithm as remote"
+ );
+ }
+ compareEffectivePoms( current.getProjectsInputInfo(), baseline.getProjectsInputInfo() );
+ compareExecutions( current.getExecutions(), baseline.getExecutions() );
+ compareFiles( current.getProjectsInputInfo(), baseline.getProjectsInputInfo() );
+ compareDependencies( current.getProjectsInputInfo(), baseline.getProjectsInputInfo() );
+
+ final BuildDiffType buildDiffType = new BuildDiffType();
+ buildDiffType.getMismatch().addAll( report );
+ return buildDiffType;
+ }
+
+ private void compareEffectivePoms( ProjectsInputInfoType current, ProjectsInputInfoType baseline )
+ {
+ Optional currentPom = findPom( current );
+ String currentPomHash = currentPom.isPresent() ? currentPom.get().getHash() : null;
+
+ Optional baseLinePom = findPom( baseline );
+ String baselinePomHash = baseLinePom.isPresent() ? baseLinePom.get().getHash() : null;
+
+ if ( !StringUtils.equals( currentPomHash, baselinePomHash ) )
+ {
+ addNewMismatch(
+ "effectivePom", currentPomHash, baselinePomHash,
+ "Difference in effective pom suggests effectively different builds which cannot be reused",
+ "Compare raw content of effective poms and eliminate differences. "
+ + "See How-To for common techniques"
+ );
+ }
+ }
+
+ public static Optional findPom( ProjectsInputInfoType projectInputs )
+ {
+ for ( DigestItemType digestItemType : projectInputs.getItem() )
+ {
+ if ( "pom".equals( digestItemType.getType() ) )
+ {
+ return Optional.of( digestItemType );
+
+ }
+ }
+ return Optional.absent();
+ }
+
+ private void compareFiles( ProjectsInputInfoType current, ProjectsInputInfoType baseline )
+ {
+
+ final Map currentFiles = new HashMap<>();
+ for ( DigestItemType item : current.getItem() )
+ {
+ if ( "file".equals( item.getType() ) )
+ {
+ currentFiles.put( item.getValue(), item );
+ }
+ }
+
+ final Map baselineFiles = new HashMap<>();
+ for ( DigestItemType item : baseline.getItem() )
+ {
+ if ( "file".equals( item.getType() ) )
+ {
+ baselineFiles.put( item.getValue(), item );
+ }
+ }
+
+ final Sets.SetView currentVsBaseline = Sets.difference( currentFiles.keySet(), baselineFiles.keySet() );
+ final Sets.SetView baselineVsCurrent = Sets.difference( baselineFiles.keySet(), currentFiles.keySet() );
+
+ if ( !currentVsBaseline.isEmpty() || !baselineVsCurrent.isEmpty() )
+ {
+ addNewMismatch( "source files",
+ "Remote and local cache contain different sets of input files. "
+ + "Added files: " + currentVsBaseline + ". Removed files: " + baselineVsCurrent,
+ "To match remote and local caches should have identical file sets."
+ + " Unnecessary and transient files must be filtered out to make file sets match"
+ + " - see configuration guide"
+ );
+ return;
+ }
+
+ for ( Map.Entry entry : currentFiles.entrySet() )
+ {
+ String filePath = entry.getKey();
+ DigestItemType currentFile = entry.getValue();
+ // should be null safe because sets are compared above for differences
+ final DigestItemType baselineFile = baselineFiles.get( filePath );
+ if ( !StringUtils.equals( currentFile.getHash(), baselineFile.getHash() ) )
+ {
+
+ String reason = "File content is different.";
+ if ( currentFile.getEol() != null && baselineFile.getEol() != null && !StringUtils.equals(
+ baselineFile.getEol(), currentFile.getEol() ) )
+ {
+ reason += " Different line endings detected (text files relevant). "
+ + "Remote: " + baselineFile.getEol() + ", local: " + currentFile.getEol() + ".";
+ }
+ if ( currentFile.getCharset() != null && baselineFile.getCharset() != null && !StringUtils.equals(
+ baselineFile.getCharset(), currentFile.getCharset() ) )
+ {
+ reason += " Different charset detected (text files relevant). "
+ + "Remote: " + baselineFile.getEol() + ", local: " + currentFile.getEol() + ".";
+ }
+
+ addNewMismatch( filePath, currentFile.getHash(), baselineFile.getHash(), reason,
+ "Different content manifests different build outcome. "
+ + "Ensure that difference is not caused by environment specifics, like line separators"
+ );
+ }
+ }
+ }
+
+ private void compareDependencies( ProjectsInputInfoType current, ProjectsInputInfoType baseline )
+ {
+ final Map currentDependencies = new HashMap<>();
+ for ( DigestItemType digestItemType : current.getItem() )
+ {
+ if ( "dependency".equals( digestItemType.getType() ) )
+ {
+ currentDependencies.put( digestItemType.getValue(), digestItemType );
+ }
+ }
+ final Map baselineDependencies = new HashMap<>();
+ for ( DigestItemType item : baseline.getItem() )
+ {
+ if ( "dependency".equals( item.getType() ) )
+ {
+ baselineDependencies.put( item.getValue(), item );
+ }
+ }
+
+ final Sets.SetView currentVsBaseline =
+ Sets.difference( currentDependencies.keySet(), baselineDependencies.keySet() );
+ final Sets.SetView baselineVsCurrent =
+ Sets.difference( baselineDependencies.keySet(), currentDependencies.keySet() );
+
+ if ( !currentVsBaseline.isEmpty() || !baselineVsCurrent.isEmpty() )
+ {
+ addNewMismatch( "dependencies files",
+ "Remote and local builds contain different sets of dependencies and cannot be matched. "
+ + "Added dependencies: " + currentVsBaseline + ". Removed dependencies: "
+ + baselineVsCurrent,
+ "Remote and local builds should have identical dependencies. "
+ + "The difference manifests changes in downstream dependencies or introduced snapshots."
+ );
+ return;
+ }
+
+ for ( Map.Entry entry : currentDependencies.entrySet() )
+ {
+ String dependencyKey = entry.getKey();
+ DigestItemType currentDependency = entry.getValue();
+ // null safe - sets compared for differences above
+ final DigestItemType baselineDependency = baselineDependencies.get( dependencyKey );
+ if ( !StringUtils.equals( currentDependency.getHash(), baselineDependency.getHash() ) )
+ {
+ addNewMismatch( dependencyKey, currentDependency.getHash(), baselineDependency.getHash(),
+ "Downstream project or snapshot changed",
+ "Find downstream project and investigate difference in the downstream project. "
+ + "Enable fail fast mode and single threaded execution to simplify debug."
+ );
+ }
+ }
+ }
+
+
+ private void compareExecutions( BuildInfoType.Executions current, BuildInfoType.Executions baseline )
+ {
+ Map baselineExecutionsByKey = new HashMap<>();
+ for ( CompletedExecutionType completedExecutionType : baseline.getExecution() )
+ {
+ baselineExecutionsByKey.put( completedExecutionType.getExecutionKey(), completedExecutionType );
+ }
+
+ Map currentExecutionsByKey = new HashMap<>();
+ for ( CompletedExecutionType e1 : current.getExecution() )
+ {
+ currentExecutionsByKey.put( e1.getExecutionKey(), e1 );
+ }
+
+ // such situation normally means different poms and mismatch in effective poms,
+ // but in any case it is helpful to report
+ for ( CompletedExecutionType baselineExecution : baseline.getExecution() )
+ {
+ if ( !currentExecutionsByKey.containsKey( baselineExecution.getExecutionKey() ) )
+ {
+ addNewMismatch(
+ baselineExecution.getExecutionKey(),
+ "Baseline build contains excessive plugin " + baselineExecution.getExecutionKey(),
+ "Different set of plugins produces different build results. "
+ + "Exclude non-critical plugins or make sure plugin sets match"
+ );
+ }
+ }
+
+ for ( CompletedExecutionType currentExecution : current.getExecution() )
+ {
+ if ( !baselineExecutionsByKey.containsKey( currentExecution.getExecutionKey() ) )
+ {
+ addNewMismatch(
+ currentExecution.getExecutionKey(),
+ "Cached build doesn't contain plugin " + currentExecution.getExecutionKey(),
+ "Different set of plugins produces different build results. "
+ + "Filter out non-critical plugins or make sure remote cache always run full build "
+ + "with all plugins"
+ );
+ continue;
+ }
+
+ final CompletedExecutionType baselineExecution =
+ baselineExecutionsByKey.get( currentExecution.getExecutionKey() );
+ comparePlugins( currentExecution, baselineExecution );
+ }
+ }
+
+ private void comparePlugins( CompletedExecutionType current, CompletedExecutionType baseline )
+ {
+ // TODO add support for skip values
+ final List trackedProperties = new ArrayList<>();
+ for ( PropertyValueType propertyValueType : current.getConfiguration().getProperty() )
+ {
+ if ( propertyValueType.isTracked() )
+ {
+ trackedProperties.add( propertyValueType );
+ }
+ }
+ if ( trackedProperties.isEmpty() )
+ {
+ return;
+ }
+
+ final Map baselinePropertiesByName = new HashMap<>();
+ for ( PropertyValueType propertyValueType : baseline.getConfiguration().getProperty() )
+ {
+ baselinePropertiesByName.put( propertyValueType.getName(), propertyValueType );
+ }
+
+ for ( PropertyValueType p : trackedProperties )
+ {
+ final PropertyValueType baselineValue = baselinePropertiesByName.get( p.getName() );
+ if ( baselineValue == null || !StringUtils.equals( baselineValue.getValue(), p.getValue() ) )
+ {
+ addNewMismatch(
+ p.getName(),
+ p.getValue(),
+ baselineValue == null ? null : baselineValue.getValue(),
+ "Plugin: " + current.getExecutionKey()
+ + " has mismatch in tracked property and cannot be reused",
+ "Align properties between remote and local build or remove property from tracked "
+ + "list if mismatch could be tolerated. In some cases it is possible to add skip value "
+ + "to ignore lax mismatch"
+ );
+ }
+ }
+ }
+
+ private void addNewMismatch( String item, String current, String baseline, String reason,
+ String resolution )
+ {
+ final MismatchType mismatch = new MismatchType();
+ mismatch.setItem( item );
+ mismatch.setCurrent( current );
+ mismatch.setBaseline( baseline );
+ mismatch.setReason( reason );
+ mismatch.setResolution( resolution );
+ report.add( mismatch );
+ }
+
+ private void addNewMismatch( String property, String reason, String resolution )
+ {
+ final MismatchType mismatchType = new MismatchType();
+ mismatchType.setItem( property );
+ mismatchType.setReason( reason );
+ mismatchType.setResolution( resolution );
+ report.add( mismatchType );
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/CacheEventSpy.java b/maven-core/src/main/java/org/apache/maven/caching/CacheEventSpy.java
new file mode 100644
index 000000000000..99f7e1a72542
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/CacheEventSpy.java
@@ -0,0 +1,49 @@
+package org.apache.maven.caching; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.eventspy.AbstractEventSpy;
+import org.apache.maven.eventspy.EventSpy;
+import org.apache.maven.execution.ExecutionEvent;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.annotations.Requirement;
+
+/**
+ * Triggers cache report generation on build completion
+ */
+@Component( role = EventSpy.class )
+public class CacheEventSpy extends AbstractEventSpy
+{
+ @Requirement
+ private CacheController cacheController;
+
+ @Override
+ public void onEvent( Object event ) throws Exception
+ {
+ if ( event instanceof ExecutionEvent )
+ {
+ ExecutionEvent executionEvent = (ExecutionEvent) event;
+ if ( executionEvent.getType() == ExecutionEvent.Type.SessionEnded )
+ {
+ cacheController.saveCacheReport( executionEvent.getSession() );
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/maven-core/src/main/java/org/apache/maven/caching/CacheResult.java b/maven-core/src/main/java/org/apache/maven/caching/CacheResult.java
new file mode 100644
index 000000000000..8d886764f8b8
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/CacheResult.java
@@ -0,0 +1,122 @@
+package org.apache.maven.caching;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.caching.xml.BuildInfo;
+import org.apache.maven.caching.xml.CacheSource;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * CacheResult
+ */
+public class CacheResult
+{
+ private final RestoreStatus status;
+ private final BuildInfo buildInfo;
+ private final CacheContext context;
+
+ private CacheResult( RestoreStatus status, BuildInfo buildInfo, CacheContext context )
+ {
+ this.status = requireNonNull( status );
+ this.buildInfo = buildInfo;
+ this.context = context;
+ }
+
+ public static CacheResult empty( CacheContext context )
+ {
+ requireNonNull( context );
+ return new CacheResult( RestoreStatus.EMPTY, null, context );
+ }
+
+ public static CacheResult empty()
+ {
+ return new CacheResult( RestoreStatus.EMPTY, null, null );
+ }
+
+ public static CacheResult failure( BuildInfo buildInfo, CacheContext context )
+ {
+ requireNonNull( buildInfo );
+ requireNonNull( context );
+ return new CacheResult( RestoreStatus.FAILURE, buildInfo, context );
+ }
+
+ public static CacheResult success( BuildInfo buildInfo, CacheContext context )
+ {
+ requireNonNull( buildInfo );
+ requireNonNull( context );
+ return new CacheResult( RestoreStatus.SUCCESS, buildInfo, context );
+ }
+
+ public static CacheResult partialSuccess( BuildInfo buildInfo, CacheContext context )
+ {
+ requireNonNull( buildInfo );
+ requireNonNull( context );
+ return new CacheResult( RestoreStatus.PARTIAL, buildInfo, context );
+ }
+
+ public static CacheResult failure( CacheContext context )
+ {
+ requireNonNull( context );
+ return new CacheResult( RestoreStatus.FAILURE, null, context );
+ }
+
+ public static CacheResult rebuilded( CacheResult orig, BuildInfo buildInfo )
+ {
+ requireNonNull( orig );
+ requireNonNull( buildInfo );
+ return new CacheResult( orig.status, buildInfo, orig.context );
+ }
+
+ public boolean isSuccess()
+ {
+ return status == RestoreStatus.SUCCESS;
+ }
+
+ public BuildInfo getBuildInfo()
+ {
+ return buildInfo;
+ }
+
+ public CacheSource getSource()
+ {
+ return buildInfo != null ? buildInfo.getSource() : null;
+ }
+
+ public CacheContext getContext()
+ {
+ return context;
+ }
+
+ public boolean isPartialSuccess()
+ {
+ return status == RestoreStatus.PARTIAL;
+ }
+
+ public RestoreStatus getStatus()
+ {
+ return status;
+ }
+
+ public boolean isFinal()
+ {
+ return buildInfo != null && buildInfo.getDto().isFinal();
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/Clock.java b/maven-core/src/main/java/org/apache/maven/caching/Clock.java
new file mode 100644
index 000000000000..ce0c487c669c
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/Clock.java
@@ -0,0 +1,42 @@
+package org.apache.maven.caching;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+
+/**
+ * Clock
+ */
+public class Clock
+{
+ public static long time()
+ {
+ return System.nanoTime();
+ }
+
+ public static long elapsed( long time )
+ {
+ return NANOSECONDS.toMillis( time() - time );
+ }
+
+ private Clock()
+ {
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/DefaultPluginScanConfig.java b/maven-core/src/main/java/org/apache/maven/caching/DefaultPluginScanConfig.java
new file mode 100644
index 000000000000..408d097cf10e
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/DefaultPluginScanConfig.java
@@ -0,0 +1,63 @@
+package org.apache.maven.caching;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.caching.jaxb.DirScanConfigType;
+
+import javax.annotation.Nonnull;
+
+/**
+ * DefaultPluginScanConfig
+ */
+public class DefaultPluginScanConfig implements PluginScanConfig
+{
+
+ @Override
+ public boolean isSkip()
+ {
+ return false;
+ }
+
+ @Override
+ public boolean accept( String propertyName )
+ {
+ return true;
+ }
+
+ @Override
+ @Nonnull
+ public PluginScanConfig mergeWith( PluginScanConfig overrideSource )
+ {
+ return overrideSource;
+ }
+
+ @Nonnull
+ @Override
+ public ScanConfigProperties getTagScanProperties( String tagName )
+ {
+ return new ScanConfigProperties( true, "*" );
+ }
+
+ @Override
+ public DirScanConfigType dto()
+ {
+ return null;
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/HttpRepositoryImpl.java b/maven-core/src/main/java/org/apache/maven/caching/HttpRepositoryImpl.java
new file mode 100644
index 000000000000..fbbe509bb28d
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/HttpRepositoryImpl.java
@@ -0,0 +1,336 @@
+package org.apache.maven.caching;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import com.google.common.base.Optional;
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpHead;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.entity.InputStreamEntity;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.caching.checksum.MavenProjectInput;
+import org.apache.maven.caching.jaxb.ArtifactType;
+import org.apache.maven.caching.jaxb.BuildInfoType;
+import org.apache.maven.caching.jaxb.CacheReportType;
+import org.apache.maven.caching.jaxb.ProjectReportType;
+import org.apache.maven.caching.xml.BuildInfo;
+import org.apache.maven.caching.xml.CacheConfig;
+import org.apache.maven.caching.xml.CacheSource;
+import org.apache.maven.caching.xml.XmlService;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.plugin.LegacySupport;
+import org.apache.maven.project.MavenProject;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.logging.Logger;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * HttpRepositoryImpl
+ */
+@Component( role = RemoteArtifactsRepository.class )
+public class HttpRepositoryImpl implements RemoteArtifactsRepository
+{
+
+ public static final String BUILDINFO_XML = "buildinfo.xml";
+ public static final String CACHE_REPORT_XML = "cache-report.xml";
+
+ @Requirement
+ private Logger logger;
+
+ @Requirement
+ LegacySupport legacySupport;
+
+ @Requirement
+ XmlService xmlService;
+
+ @Requirement
+ private CacheConfig cacheConfig;
+
+ @SuppressWarnings( {"checkstyle:constantname", "checkstyle:magicnumber"} )
+ private static final ThreadLocal httpClient = new ThreadLocal()
+ {
+ @Override
+ protected HttpClient initialValue()
+ {
+ int timeoutSeconds = 60;
+ RequestConfig config = RequestConfig.custom().setConnectTimeout(
+ timeoutSeconds * 1000 ).setConnectionRequestTimeout( timeoutSeconds * 1000 ).setSocketTimeout(
+ timeoutSeconds * 1000 ).build();
+ return HttpClientBuilder.create().setDefaultRequestConfig( config ).build();
+ }
+ };
+
+ @Override
+ public BuildInfo findBuild( CacheContext context )
+ {
+ final String resourceUrl = getResourceUrl( context, BUILDINFO_XML );
+ String artifactId = context.getProject().getArtifactId();
+ if ( exists( artifactId, resourceUrl ) )
+ {
+ final byte[] bytes = getResourceContent( resourceUrl, artifactId );
+ final BuildInfoType dto = xmlService.fromBytes( BuildInfoType.class, bytes );
+ return new BuildInfo( dto, CacheSource.REMOTE );
+ }
+ return null;
+ }
+
+ @Override
+ public byte[] getArtifactContent( CacheContext context, ArtifactType artifact )
+ {
+ return getResourceContent( getResourceUrl( context, artifact.getFileName() ),
+ context.getProject().getArtifactId() );
+ }
+
+ @Override
+ public void saveBuildInfo( CacheResult cacheResult, BuildInfo buildInfo ) throws IOException
+ {
+ CacheContext context = cacheResult.getContext();
+ final String resourceUrl = getResourceUrl( cacheResult.getContext(), BUILDINFO_XML );
+ putToRemoteCache( new ByteArrayInputStream( xmlService.toBytes( buildInfo.getDto() ) ), resourceUrl,
+ context.getProject().getArtifactId() );
+ }
+
+
+ @Override
+ public void saveCacheReport( String buildId, MavenSession session, CacheReportType cacheReport ) throws IOException
+ {
+ MavenProject rootProject = session.getTopLevelProject();
+ final String resourceUrl = cacheConfig.getUrl() + "/" + MavenProjectInput.CACHE_IMPLMENTATION_VERSION
+ + "/" + rootProject.getGroupId()
+ + "/" + rootProject.getArtifactId()
+ + "/" + buildId
+ + "/" + CACHE_REPORT_XML;
+ putToRemoteCache( new ByteArrayInputStream( xmlService.toBytes( cacheReport ) ), resourceUrl,
+ rootProject.getArtifactId() );
+ }
+
+ @Override
+ public void saveArtifactFile( CacheResult cacheResult, Artifact artifact ) throws IOException
+ {
+ CacheContext context = cacheResult.getContext();
+ final String resourceUrl = getResourceUrl( cacheResult.getContext(), ProjectUtils.normalizedName( artifact ) );
+ try ( InputStream inputStream = Files.newInputStream( artifact.getFile().toPath() ) )
+ {
+ putToRemoteCache( inputStream, resourceUrl, context.getProject().getArtifactId() );
+ }
+ }
+
+ @SuppressWarnings( "checkstyle:magicnumber" )
+ private boolean exists( String logReference, String url )
+ {
+ HttpHead head = null;
+ try
+ {
+ head = new HttpHead( url );
+ HttpResponse response = httpClient.get().execute( head );
+ int statusCode = response.getStatusLine().getStatusCode();
+ logger.info( "[CACHE][" + logReference + "] Checking " + url + ". Status: " + statusCode );
+ return statusCode == 200;
+ }
+ catch ( IOException e )
+ {
+ throw new RuntimeException( "Cannot check " + url, e );
+ }
+ finally
+ {
+ if ( head != null )
+ {
+ head.releaseConnection();
+ }
+ }
+ }
+
+ @SuppressWarnings( "checkstyle:magicnumber" )
+ public byte[] getResourceContent( String url, String logReference )
+ {
+ HttpGet get = null;
+ try
+ {
+ get = new HttpGet( url );
+ HttpResponse response = httpClient.get().execute( get );
+ int statusCode = response.getStatusLine().getStatusCode();
+ logger.info( "[CACHE][" + logReference + "] Downloading " + url + ". Status: " + statusCode );
+ if ( statusCode != 200 )
+ {
+ throw new RuntimeException( "Cannot download " + url + ", unexpected status code: " + statusCode );
+ }
+ try ( InputStream content = response.getEntity().getContent() )
+ {
+ return IOUtils.toByteArray( content );
+ }
+ }
+ catch ( IOException e )
+ {
+ throw new RuntimeException( "Cannot get " + url, e );
+ }
+ finally
+ {
+ if ( get != null )
+ {
+ get.releaseConnection();
+ }
+ }
+ }
+
+ @Override
+ public String getResourceUrl( CacheContext context, String filename )
+ {
+ return getResourceUrl( filename, context.getProject().getGroupId(), context.getProject().getArtifactId(),
+ context.getInputInfo().getChecksum() );
+ }
+
+ private String getResourceUrl( String filename, String groupId, String artifactId, String checksum )
+ {
+ return cacheConfig.getUrl() + "/" + MavenProjectInput.CACHE_IMPLMENTATION_VERSION + "/" + groupId + "/"
+ + artifactId + "/" + checksum + "/" + filename;
+ }
+
+ /**
+ * @param logReference
+ * @param instream to be closed externally
+ */
+ private void putToRemoteCache( InputStream instream, String url, String logReference ) throws IOException
+ {
+
+ HttpPut httpPut = null;
+ try
+ {
+ httpPut = new HttpPut( url );
+ httpPut.setEntity( new InputStreamEntity( instream ) );
+ HttpResponse response = httpClient.get().execute( httpPut );
+ int statusCode = response.getStatusLine().getStatusCode();
+ logInfo( "Saved to remote cache " + url + ". RESPONSE CODE: " + statusCode, logReference );
+ }
+ finally
+ {
+ if ( httpPut != null )
+ {
+ httpPut.releaseConnection();
+ }
+ }
+ }
+
+ private final AtomicReference>> cacheReportSupplier = new AtomicReference<>();
+
+ @Override
+ public Optional findBaselineBuild( MavenProject project )
+ {
+ final Optional> cachedProjectsHolder = findCacheInfo()
+ .transform( CacheReportType::getProject );
+ if ( !cachedProjectsHolder.isPresent() )
+ {
+ return Optional.absent();
+ }
+
+ Optional cachedProjectHolder = Optional.absent();
+ for ( ProjectReportType p : cachedProjectsHolder.get() )
+ {
+ if ( project.getArtifactId().equals( p.getArtifactId() ) && project.getGroupId().equals(
+ p.getGroupId() ) )
+ {
+ cachedProjectHolder = Optional.of( p );
+ break;
+ }
+ }
+
+ if ( cachedProjectHolder.isPresent() )
+ {
+ String url;
+ final ProjectReportType projectReport = cachedProjectHolder.get();
+ if ( projectReport.isSetUrl() )
+ {
+ url = cachedProjectHolder.get().getUrl();
+ logInfo( "Retrieving baseline buildinfo: " + projectReport.getUrl(), project.getArtifactId() );
+ }
+ else
+ {
+ url = getResourceUrl( BUILDINFO_XML, project.getGroupId(),
+ project.getArtifactId(), projectReport.getChecksum() );
+ logInfo( "Baseline project record doesn't have url, trying default location", project.getArtifactId() );
+ }
+
+ try
+ {
+ if ( exists( project.getArtifactId(), url ) )
+ {
+ byte[] content = getResourceContent( url, project.getArtifactId() );
+ final BuildInfoType dto = xmlService.fromBytes( BuildInfoType.class, content );
+ return Optional.of( new BuildInfo( dto, CacheSource.REMOTE ) );
+ }
+ else
+ {
+ logInfo( "Project buildinfo not found, skipping diff",
+ project.getArtifactId() );
+ }
+ }
+ catch ( Exception e )
+ {
+ logger.warn( "[CACHE][" + project.getArtifactId() + "] Error restoring baseline build at url: "
+ + projectReport.getUrl() + ", skipping diff" );
+ return Optional.absent();
+ }
+ }
+ return Optional.absent();
+ }
+
+ private Optional findCacheInfo()
+ {
+
+ Supplier> candidate = Suppliers.memoize( () ->
+ {
+ try
+ {
+ logInfo( "Downloading baseline cache report from: " + cacheConfig.getBaselineCacheUrl(),
+ "DEBUG" );
+ byte[] content = getResourceContent( cacheConfig.getBaselineCacheUrl(), "cache-info" );
+ CacheReportType cacheReportType = xmlService.fromBytes( CacheReportType.class, content );
+ return Optional.of( cacheReportType );
+ }
+ catch ( Exception e )
+ {
+ logger.error( "Error downloading baseline report from: " + cacheConfig.getBaselineCacheUrl()
+ + ", skipping diff.", e );
+ return Optional.absent();
+ }
+ } );
+ cacheReportSupplier.compareAndSet( null, candidate );
+
+ return cacheReportSupplier.get().get();
+ }
+
+ private void logInfo( String message, String logReference )
+ {
+ logger.info( "[CACHE][" + logReference + "] " + message );
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/LocalArtifactsRepository.java b/maven-core/src/main/java/org/apache/maven/caching/LocalArtifactsRepository.java
new file mode 100644
index 000000000000..c5916b2c64ba
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/LocalArtifactsRepository.java
@@ -0,0 +1,47 @@
+package org.apache.maven.caching;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import com.google.common.base.Optional;
+import org.apache.maven.caching.jaxb.ArtifactType;
+import org.apache.maven.caching.xml.BuildInfo;
+import org.apache.maven.caching.xml.CacheSource;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.model.Dependency;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+/**
+ * LocalArtifactsRepository
+ */
+public interface LocalArtifactsRepository extends ArtifactsRepository
+{
+
+ void beforeSave( CacheContext environment ) throws IOException;
+
+ Path getArtifactFile( CacheContext context, CacheSource source, ArtifactType artifact ) throws IOException;
+
+ void clearCache( CacheContext context );
+
+ Optional findBestMatchingBuild( MavenSession session, Dependency dependency ) throws IOException;
+
+ BuildInfo findLocalBuild( CacheContext context ) throws IOException;
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/LocalRepositoryImpl.java b/maven-core/src/main/java/org/apache/maven/caching/LocalRepositoryImpl.java
new file mode 100644
index 000000000000..ec75b476e4b4
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/LocalRepositoryImpl.java
@@ -0,0 +1,496 @@
+package org.apache.maven.caching;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Ordering;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.caching.jaxb.ArtifactType;
+import org.apache.maven.caching.jaxb.BuildInfoType;
+import org.apache.maven.caching.jaxb.CacheReportType;
+import org.apache.maven.caching.xml.BuildInfo;
+import org.apache.maven.caching.xml.CacheConfig;
+import org.apache.maven.caching.xml.CacheSource;
+import org.apache.maven.caching.xml.XmlService;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.plugin.LegacySupport;
+import org.apache.maven.project.MavenProject;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.logging.Logger;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedList;
+import java.util.List;
+
+import static java.nio.file.StandardOpenOption.CREATE;
+import static java.nio.file.StandardOpenOption.CREATE_NEW;
+import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
+import static java.util.concurrent.TimeUnit.DAYS;
+import static java.util.concurrent.TimeUnit.HOURS;
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
+import static org.apache.maven.caching.ProjectUtils.getMultimoduleRoot;
+import static org.apache.maven.caching.checksum.MavenProjectInput.CACHE_IMPLMENTATION_VERSION;
+
+/**
+ * LocalRepositoryImpl
+ */
+@Component( role = LocalArtifactsRepository.class )
+public class LocalRepositoryImpl implements LocalArtifactsRepository
+{
+
+ private static final String BUILDINFO_XML = "buildinfo.xml";
+ private static final String LOOKUPINFO_XML = "lookupinfo.xml";
+ private static final long ONE_HOUR_MILLIS = HOURS.toMillis( 1 );
+ private static final long ONE_MINUTE_MILLIS = MINUTES.toMillis( 1 );
+ private static final long ONE_DAY_MILLIS = DAYS.toMillis( 1 );
+ private static final String EMPTY = "";
+ private static final LastModifiedComparator LAST_MODIFIED_COMPARATOR = new LastModifiedComparator();
+ private static final Function, Long> GET_LAST_MODIFIED =
+ pair -> pair.getRight().lastModified();
+
+ @Requirement
+ private Logger logger;
+
+ @Requirement
+ private LegacySupport legacySupport;
+
+ @Requirement
+ private RemoteArtifactsRepository remoteRepository;
+
+ @Requirement
+ private XmlService xmlService;
+
+ @Requirement
+ private CacheConfig cacheConfig;
+
+ private final LoadingCache, Optional> bestBuildCache =
+ CacheBuilder.newBuilder().build(
+ CacheLoader.from( new Function, Optional>()
+ {
+ @Override
+ public Optional apply( Pair input )
+ {
+ try
+ {
+ return findBestMatchingBuildImpl( input );
+ }
+ catch ( IOException e )
+ {
+ logger.error( "Cannot find dependency in cache", e );
+ return Optional.absent();
+ }
+ }
+ } ) );
+
+ @Override
+ public BuildInfo findLocalBuild( CacheContext context ) throws IOException
+ {
+ Path localBuildInfoPath = localBuildPath( context, BUILDINFO_XML, false );
+ logDebug( context, "Checking local build info: " + localBuildInfoPath );
+ if ( Files.exists( localBuildInfoPath ) )
+ {
+ logInfo( context, "Local build found by checksum " + context.getInputInfo().getChecksum() );
+ try
+ {
+ final BuildInfoType dto = xmlService.fromFile( BuildInfoType.class, localBuildInfoPath.toFile() );
+ return new BuildInfo( dto, CacheSource.LOCAL );
+ }
+ catch ( Exception e )
+ {
+ logger.error( "Local build info is not valid, deleting: " + localBuildInfoPath, e );
+ Files.delete( localBuildInfoPath );
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public BuildInfo findBuild( CacheContext context ) throws IOException
+ {
+
+ Path buildInfoPath = remoteBuildPath( context, BUILDINFO_XML );
+ logDebug( context, "Checking if build is already downloaded: " + buildInfoPath );
+
+ if ( Files.exists( buildInfoPath ) )
+ {
+ logInfo( context, "Downloaded build found by checksum " + context.getInputInfo().getChecksum() );
+ try
+ {
+ final BuildInfoType dto = xmlService.fromFile( BuildInfoType.class, buildInfoPath.toFile() );
+ return new BuildInfo( dto, CacheSource.REMOTE );
+ }
+ catch ( Exception e )
+ {
+ logger.error( "Downloaded build info is not valid, deleting: " + buildInfoPath, e );
+ Files.delete( buildInfoPath );
+ }
+ }
+
+ if ( !cacheConfig.isRemoteCacheEnabled() )
+ {
+ return null;
+ }
+
+ try
+ {
+
+ Path lookupInfoPath = remoteBuildPath( context, LOOKUPINFO_XML );
+ if ( Files.exists( lookupInfoPath ) )
+ {
+ final BasicFileAttributes fileAttributes = Files.readAttributes( lookupInfoPath,
+ BasicFileAttributes.class );
+ final long lastModified = fileAttributes.lastModifiedTime().toMillis();
+ final long created = fileAttributes.creationTime().toMillis();
+ final long now = System.currentTimeMillis();
+ // throttle remote cache calls, maven like
+ if ( now < created + ONE_HOUR_MILLIS && now < lastModified + ONE_MINUTE_MILLIS )
+ { // fresh file, allow lookup every minute
+ logInfo( context, "Skipping remote lookup, last unsuccessful lookup less than 1m ago." );
+ return null;
+ }
+ else if ( now < created + ONE_DAY_MILLIS && now < lastModified + ONE_HOUR_MILLIS )
+ { // less than 1 day file, allow 1 per hour lookup
+ logInfo( context, "Skipping remote lookup, last unsuccessful lookup less than 1h ago." );
+ return null;
+ }
+ else if ( now > created + ONE_DAY_MILLIS && now < lastModified + ONE_DAY_MILLIS )
+ { // more than 1 day file, allow 1 per day lookup
+ logInfo( context, "Skipping remote lookup, last unsuccessful lookup less than 1d ago." );
+ return null;
+ }
+ }
+
+ final BuildInfo buildInfo = remoteRepository.findBuild( context );
+ if ( buildInfo != null )
+ {
+ logInfo( context, "Build info downloaded from remote repo, saving to: " + buildInfoPath );
+ Files.createDirectories( buildInfoPath.getParent() );
+ Files.write( buildInfoPath, xmlService.toBytes( buildInfo.getDto() ), CREATE_NEW );
+ }
+ else
+ {
+ FileUtils.touch( lookupInfoPath.toFile() );
+ }
+ return buildInfo;
+ }
+ catch ( Exception e )
+ {
+ logger.error( "Remote build info is not valid, cached data is not compatible", e );
+ return null;
+ }
+ }
+
+ @Override
+ public void clearCache( CacheContext context )
+ {
+ try
+ {
+ final Path buildCacheDir = buildCacheDir( context );
+ Path artifactCacheDir = buildCacheDir.getParent();
+
+ if ( !Files.exists( artifactCacheDir ) )
+ {
+ return;
+ }
+
+ List cacheDirs = new ArrayList<>();
+ for ( Path dir : Files.newDirectoryStream( artifactCacheDir ) )
+ {
+ if ( Files.isDirectory( dir ) )
+ {
+ cacheDirs.add( dir );
+ }
+ }
+ if ( cacheDirs.size() > cacheConfig.getMaxLocalBuildsCached() )
+ {
+ Collections.sort( cacheDirs, LAST_MODIFIED_COMPARATOR );
+ for ( Path dir : cacheDirs.subList( 0, cacheDirs.size() - cacheConfig.getMaxLocalBuildsCached() ) )
+ {
+ FileUtils.deleteDirectory( dir.toFile() );
+ }
+ }
+ final Path path = localBuildDir( context );
+ if ( Files.exists( path ) )
+ {
+ FileUtils.deleteDirectory( path.toFile() );
+ }
+ }
+ catch ( IOException e )
+ {
+ final String artifactId = context.getProject().getArtifactId();
+ throw new RuntimeException(
+ "Failed to cleanup local cache of " + artifactId + " on build failure, it might be inconsistent",
+ e );
+ }
+ }
+
+ @Override
+ public Optional findBestMatchingBuild( MavenSession session, Dependency dependency )
+ {
+ return bestBuildCache.getUnchecked( Pair.of( session, dependency ) );
+ }
+
+
+ private Optional findBestMatchingBuildImpl( Pair dependencySession )
+ throws IOException
+ {
+ final MavenSession session = dependencySession.getLeft();
+ final Dependency dependency = dependencySession.getRight();
+
+ final Path artifactCacheDir = artifactCacheDir( session, dependency.getGroupId(), dependency.getArtifactId() );
+
+ final Multimap, Pair> filesByVersion = ArrayListMultimap.create();
+
+ Files.walkFileTree( artifactCacheDir, new SimpleFileVisitor()
+ {
+ @Override
+ public FileVisitResult visitFile( Path o, BasicFileAttributes basicFileAttributes )
+ {
+ final File file = o.toFile();
+ if ( file.getName().equals( BUILDINFO_XML ) )
+ {
+ try
+ {
+ final BuildInfoType dto = xmlService.fromFile( BuildInfoType.class, file );
+ final Pair buildInfoAndFile = Pair.of( new BuildInfo( dto, CacheSource.LOCAL ),
+ file );
+ final String cachedVersion = dto.getArtifact().getVersion();
+ final String cachedBranch = getScmRef( dto.getScm() );
+ filesByVersion.put( Pair.of( cachedVersion, cachedBranch ), buildInfoAndFile );
+ if ( isNotBlank( cachedBranch ) )
+ {
+ filesByVersion.put( Pair.of( EMPTY, cachedBranch ), buildInfoAndFile );
+ }
+ if ( isNotBlank( cachedVersion ) )
+ {
+ filesByVersion.put( Pair.of( cachedVersion, EMPTY ), buildInfoAndFile );
+ }
+ }
+ catch ( Exception e )
+ {
+ // version is unusable nothing we can do here
+ logger.error( "Build info is not compatible to current maven implementation: " + file );
+ }
+ }
+ return FileVisitResult.CONTINUE;
+ }
+ } );
+
+ if ( filesByVersion.isEmpty() )
+ {
+ return Optional.absent();
+ }
+
+ final String currentRef = getScmRef( ProjectUtils.readGitInfo( session ) );
+ // first lets try by branch and version
+ Collection> bestMatched = new LinkedList<>();
+ if ( isNotBlank( currentRef ) )
+ {
+ bestMatched = filesByVersion.get( Pair.of( dependency.getVersion(), currentRef ) );
+ }
+ if ( Iterables.isEmpty( bestMatched ) )
+ {
+ // then by version
+ bestMatched = filesByVersion.get( Pair.of( dependency.getVersion(), EMPTY ) );
+ }
+ if ( Iterables.isEmpty( bestMatched ) && isNotBlank( currentRef ) )
+ {
+ // then by branch
+ bestMatched = filesByVersion.get( Pair.of( EMPTY, currentRef ) );
+ }
+ if ( Iterables.isEmpty( bestMatched ) )
+ {
+ // ok lets take all
+ bestMatched = filesByVersion.values();
+ }
+
+ List> orderedFiles = Ordering.natural().onResultOf(
+ GET_LAST_MODIFIED ).reverse().sortedCopy( bestMatched );
+ return Optional.of( orderedFiles.get( 0 ).getLeft() );
+ }
+
+ private String getScmRef( BuildInfoType.Scm scm )
+ {
+ if ( scm != null )
+ {
+ return scm.isSetSourceBranch() ? scm.getSourceBranch() : scm.getRevision();
+ }
+ else
+ {
+ return EMPTY;
+ }
+ }
+
+ @Override
+ public Path getArtifactFile( CacheContext context, CacheSource source, ArtifactType artifact ) throws IOException
+ {
+ if ( source == CacheSource.LOCAL )
+ {
+ return localBuildPath( context, artifact.getFileName(), false );
+ }
+ else
+ {
+ Path cachePath = remoteBuildPath( context, artifact.getFileName() );
+ if ( !Files.exists( cachePath ) && cacheConfig.isRemoteCacheEnabled() )
+ {
+ final byte[] artifactContent = remoteRepository.getArtifactContent( context, artifact );
+ if ( artifactContent != null )
+ {
+ Files.write( cachePath, artifactContent, CREATE_NEW );
+ }
+ }
+ return cachePath;
+ }
+ }
+
+ @Override
+ public void beforeSave( CacheContext environment )
+ {
+ clearCache( environment );
+ }
+
+ @Override
+ public void saveBuildInfo( CacheResult cacheResult, BuildInfo buildInfo ) throws IOException
+ {
+ final Path path = localBuildPath( cacheResult.getContext(), BUILDINFO_XML, true );
+ Files.write( path, xmlService.toBytes( buildInfo.getDto() ), TRUNCATE_EXISTING, CREATE );
+ if ( cacheConfig.isRemoteCacheEnabled() && cacheConfig.isSaveToRemote() && !cacheResult.isFinal() )
+ {
+ remoteRepository.saveBuildInfo( cacheResult, buildInfo );
+ }
+ }
+
+ @Override
+ public void saveCacheReport( String buildId, MavenSession session, CacheReportType cacheReport ) throws IOException
+ {
+ Path path = Paths.get( getMultimoduleRoot( session ), "target", "maven-incremental" );
+ Files.createDirectories( path );
+ Files.write( path.resolve( "cache-report." + buildId + ".xml" ), xmlService.toBytes( cacheReport ),
+ TRUNCATE_EXISTING, CREATE );
+ if ( cacheConfig.isRemoteCacheEnabled() && cacheConfig.isSaveToRemote() )
+ {
+ logger.info( "[CACHE] Saving cache report on build completion" );
+ remoteRepository.saveCacheReport( buildId, session, cacheReport );
+ }
+ }
+
+ @Override
+ public void saveArtifactFile( CacheResult cacheResult, Artifact artifact ) throws IOException
+ {
+ // safe artifacts to cache
+ File artifactFile = artifact.getFile();
+ Path cachePath = localBuildPath( cacheResult.getContext(), ProjectUtils.normalizedName( artifact ), true );
+ Files.copy( artifactFile.toPath(), cachePath, StandardCopyOption.REPLACE_EXISTING );
+ if ( cacheConfig.isRemoteCacheEnabled() && cacheConfig.isSaveToRemote() && !cacheResult.isFinal() )
+ {
+ remoteRepository.saveArtifactFile( cacheResult, artifact );
+ }
+ }
+
+ private Path buildCacheDir( CacheContext context ) throws IOException
+ {
+ final MavenProject project = context.getProject();
+ final Path artifactCacheDir = artifactCacheDir( context.getSession(), project.getGroupId(),
+ project.getArtifactId() );
+ return artifactCacheDir.resolve( context.getInputInfo().getChecksum() );
+ }
+
+ private Path artifactCacheDir( MavenSession session, String groupId, String artifactId ) throws IOException
+ {
+ final String localRepositoryRoot = session.getLocalRepository().getBasedir();
+ final Path path = Paths.get( localRepositoryRoot, "..", "cache", CACHE_IMPLMENTATION_VERSION, groupId,
+ artifactId ).normalize();
+ if ( !Files.exists( path ) )
+ {
+ Files.createDirectories( path );
+ }
+ return path;
+ }
+
+ private Path remoteBuildPath( CacheContext context, String filename ) throws IOException
+ {
+ return buildCacheDir( context ).resolve( filename );
+ }
+
+ private Path localBuildPath( CacheContext context, String filename, boolean createDir ) throws IOException
+ {
+ final Path localBuildDir = localBuildDir( context );
+ if ( createDir )
+ {
+ Files.createDirectories( localBuildDir );
+ }
+ return localBuildDir.resolve( filename );
+ }
+
+ private Path localBuildDir( CacheContext context ) throws IOException
+ {
+ return buildCacheDir( context ).resolve( "local" );
+ }
+
+ private void logDebug( CacheContext context, String message )
+ {
+ logger.debug( "[CACHE][" + context.getProject().getArtifactId() + "] " + message );
+ }
+
+ private void logInfo( CacheContext context, String message )
+ {
+ logger.info( "[CACHE][" + context.getProject().getArtifactId() + "] " + message );
+ }
+
+ private static class LastModifiedComparator implements Comparator
+ {
+ @Override
+ public int compare( Path p1, Path p2 )
+ {
+ try
+ {
+ return Files.getLastModifiedTime( p1 ).compareTo( Files.getLastModifiedTime( p2 ) );
+ }
+ catch ( IOException e )
+ {
+ return 0;
+ }
+ }
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/MojoExecutionManager.java b/maven-core/src/main/java/org/apache/maven/caching/MojoExecutionManager.java
new file mode 100644
index 000000000000..9c07e5815e69
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/MojoExecutionManager.java
@@ -0,0 +1,178 @@
+package org.apache.maven.caching;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.maven.caching.jaxb.CompletedExecutionType;
+import org.apache.maven.caching.jaxb.TrackedPropertyType;
+import org.apache.maven.caching.xml.BuildInfo;
+import org.apache.maven.caching.xml.CacheConfig;
+import org.apache.maven.caching.xml.DtoUtils;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.plugin.Mojo;
+import org.apache.maven.plugin.MojoCheker;
+import org.apache.maven.plugin.MojoExecution;
+import org.apache.maven.project.MavenProject;
+import org.codehaus.plexus.logging.Logger;
+import org.codehaus.plexus.util.ReflectionUtils;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.apache.maven.caching.ProjectUtils.mojoExecutionKey;
+
+/**
+ * MojoExecutionManager
+ */
+public class MojoExecutionManager implements MojoCheker
+{
+
+ private final long createdTimestamp;
+ private final Logger logger;
+ private final MavenProject project;
+ private final BuildInfo buildInfo;
+ private final AtomicBoolean consistent;
+ private final CacheController cacheController;
+ private final CacheConfig cacheConfig;
+
+ public MojoExecutionManager( MavenProject project,
+ CacheController cacheController,
+ BuildInfo buildInfo,
+ AtomicBoolean consistent,
+ Logger logger, CacheConfig cacheConfig )
+ {
+ this.createdTimestamp = System.currentTimeMillis();
+ this.project = project;
+ this.cacheController = cacheController;
+ this.buildInfo = buildInfo;
+ this.consistent = consistent;
+ this.logger = logger;
+ this.cacheConfig = cacheConfig;
+ }
+
+ /**
+ * runtime check is rather expensive for cached build and better to be avoided when possible
+ */
+ @Override
+ public boolean needCheck( MojoExecution mojoExecution, MavenSession session )
+ {
+ return !cacheConfig.getTrackedProperties( mojoExecution ).isEmpty();
+ }
+
+ /**
+ * this implementation has side effect of consistency check to force local save if local run is different than
+ * cached
+ *
+ * @return false always returns false to prevent mojo execution
+ */
+ @Override
+ public boolean check( MojoExecution execution, Mojo mojo, MavenSession session )
+ {
+
+ final CompletedExecutionType completedExecution = buildInfo.findMojoExecutionInfo( execution );
+ final String fullGoalName = execution.getMojoDescriptor().getFullGoalName();
+
+ if ( completedExecution != null && !isParamsMatched( project, execution, mojo, completedExecution ) )
+ {
+ logInfo( project,
+ "Mojo cached parameters mismatch with actual, forcing full project build. Mojo: " + fullGoalName );
+ consistent.set( false );
+ }
+
+ if ( consistent.get() )
+ {
+ long elapsed = System.currentTimeMillis() - createdTimestamp;
+ logInfo( project, "Skipping plugin execution (reconciled in " + elapsed + " millis): " + fullGoalName );
+ }
+
+ if ( logger.isDebugEnabled() )
+ {
+ logger.debug(
+ "[CACHE][" + project.getArtifactId() + "] Checked " + fullGoalName + ", resolved mojo: " + mojo
+ + ", cached params:" + completedExecution );
+ }
+ return false;
+ }
+
+ private boolean isParamsMatched( MavenProject project,
+ MojoExecution mojoExecution,
+ Mojo mojo,
+ CompletedExecutionType completedExecution )
+ {
+
+ List tracked = cacheConfig.getTrackedProperties( mojoExecution );
+
+ for ( TrackedPropertyType trackedProperty : tracked )
+ {
+ final String propertyName = trackedProperty.getPropertyName();
+
+ String expectedValue = DtoUtils.findPropertyValue( propertyName, completedExecution );
+ if ( expectedValue == null && trackedProperty.isSetDefaultValue() )
+ {
+ expectedValue = trackedProperty.getDefaultValue();
+ }
+
+ final String currentValue;
+ try
+ {
+ currentValue = String.valueOf( ReflectionUtils.getValueIncludingSuperclasses( propertyName, mojo ) );
+ }
+ catch ( IllegalAccessException e )
+ {
+ logError( project, "Cannot extract plugin property " + propertyName + " from mojo " + mojo, e );
+ return false;
+ }
+
+ if ( !StringUtils.equals( currentValue, expectedValue ) )
+ {
+ if ( !StringUtils.equals( currentValue, trackedProperty.getSkipValue() ) )
+ {
+ logInfo( project,
+ "Plugin parameter mismatch found. Parameter: " + propertyName + ", expected: "
+ + expectedValue + ", actual: " + currentValue );
+ return false;
+ }
+ else
+ {
+ logWarn( project,
+ "Cache contains plugin execution with skip flag and might be incomplete. Property: "
+ + propertyName + ", execution: " + mojoExecutionKey( mojoExecution ) );
+ }
+ }
+ }
+ return true;
+ }
+
+ private void logInfo( MavenProject project, String message )
+ {
+ logger.info( "[CACHE][" + project.getArtifactId() + "] " + message );
+ }
+
+ private void logError( MavenProject project, String message, Exception e )
+ {
+ logger.error( "[CACHE][" + project.getArtifactId() + "] " + message, e );
+ }
+
+ private void logWarn( MavenProject project, String message )
+ {
+ logger.warn( "[CACHE][" + project.getArtifactId() + "] " + message );
+ }
+
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/MojoParametersListener.java b/maven-core/src/main/java/org/apache/maven/caching/MojoParametersListener.java
new file mode 100644
index 000000000000..8750a8dcca67
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/MojoParametersListener.java
@@ -0,0 +1,102 @@
+package org.apache.maven.caching;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.execution.MojoExecutionEvent;
+import org.apache.maven.execution.MojoExecutionListener;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.project.MavenProject;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.logging.Logger;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * MojoParametersListener
+ */
+@Component( role = MojoExecutionListener.class,
+ hint = "MojoParametersListener" )
+public class MojoParametersListener implements MojoExecutionListener
+{
+
+ private final ConcurrentMap> projectExecutions =
+ new ConcurrentHashMap<>();
+
+ @Requirement
+ private Logger logger;
+
+ @Override
+ public void beforeMojoExecution( MojoExecutionEvent event )
+ {
+ final String executionKey = ProjectUtils.mojoExecutionKey( event.getExecution() );
+ logDebug( event.getProject(),
+ "Starting mojo execution: " + executionKey + ", class: " + event.getMojo().getClass() );
+ final MavenProject project = event.getProject();
+ Map projectEvents = projectExecutions.get( project );
+ if ( projectEvents == null )
+ {
+ Map candidate = new ConcurrentHashMap<>();
+ projectEvents = projectExecutions.putIfAbsent( project, candidate );
+ if ( projectEvents == null )
+ {
+ projectEvents = candidate;
+ }
+ }
+ projectEvents.put( executionKey, event );
+ }
+
+ @Override
+ public void afterMojoExecutionSuccess( MojoExecutionEvent event ) throws MojoExecutionException
+ {
+ // do nothing
+ }
+
+ @Override
+ public void afterExecutionFailure( MojoExecutionEvent event )
+ {
+ //do nothing
+ }
+
+ public Map getProjectExecutions( MavenProject project )
+ {
+ return projectExecutions.get( project );
+ }
+
+ public void remove( MavenProject project )
+ {
+ projectExecutions.remove( project );
+ }
+
+ private void logDebug( MavenProject project, String message )
+ {
+ if ( logger.isDebugEnabled() )
+ {
+ logger.debug( "[CACHE][" + project.getArtifactId() + "] " + message );
+ }
+ }
+
+ // private void logInfo(String message) {
+ // logger.info("[CACHE][" + project.getArtifactId() + "] " + message);
+ // }
+
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/PluginScanConfig.java b/maven-core/src/main/java/org/apache/maven/caching/PluginScanConfig.java
new file mode 100644
index 000000000000..93bcccba0b1c
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/PluginScanConfig.java
@@ -0,0 +1,41 @@
+package org.apache.maven.caching;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.caching.jaxb.DirScanConfigType;
+
+import javax.annotation.Nonnull;
+
+/**
+ * PluginScanConfig
+ */
+public interface PluginScanConfig
+{
+ boolean isSkip();
+
+ boolean accept( String propertyName );
+
+ PluginScanConfig mergeWith( PluginScanConfig overrideSource );
+
+ @Nonnull
+ ScanConfigProperties getTagScanProperties( String tagName );
+
+ DirScanConfigType dto();
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/PluginScanConfigImpl.java b/maven-core/src/main/java/org/apache/maven/caching/PluginScanConfigImpl.java
new file mode 100644
index 000000000000..87791905100d
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/PluginScanConfigImpl.java
@@ -0,0 +1,161 @@
+package org.apache.maven.caching;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.maven.caching.jaxb.DirScanConfigType;
+import org.apache.maven.caching.jaxb.TagNameType;
+import org.apache.maven.caching.jaxb.TagScanConfigType;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+
+/**
+ * PluginScanConfigImpl
+ */
+public class PluginScanConfigImpl implements PluginScanConfig
+{
+
+ private final DirScanConfigType dto;
+
+ public PluginScanConfigImpl( DirScanConfigType scanConfig )
+ {
+ this.dto = scanConfig;
+ }
+
+ @Override
+ public boolean isSkip()
+ {
+ return StringUtils.equals( dto.getMode(), "skip" );
+ }
+
+ @Override
+ public boolean accept( String tagName )
+ {
+ // include or exclude is a choice element, could be only obe property set
+
+ //noinspection ConstantConditions
+ final List includes = dto.getInclude();
+ if ( !includes.isEmpty() )
+ {
+ return findTagScanProperties( tagName ) != null;
+ }
+
+ return !contains( dto.getExclude(), tagName );
+ }
+
+ private boolean contains( List excludes, String tagName )
+ {
+ for ( TagNameType exclude : excludes )
+ {
+ if ( StringUtils.equals( exclude.getTagName(), tagName ) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Nonnull
+ @Override
+ public PluginScanConfig mergeWith( final PluginScanConfig overrideConfig )
+ {
+
+ if ( dto == null )
+ {
+ return overrideConfig;
+ }
+
+ final DirScanConfigType override = overrideConfig.dto();
+ if ( override == null )
+ {
+ return this;
+ }
+
+ if ( override.isIgnoreParent() )
+ {
+ return overrideConfig;
+ }
+
+ DirScanConfigType merged = new DirScanConfigType();
+ if ( override.isSetMode() )
+ {
+ merged.setMode( override.getMode() );
+ }
+ else
+ {
+ merged.setMode( dto.getMode() );
+ }
+
+ merged.getExclude().addAll( dto.getExclude() );
+ merged.getExclude().addAll( override.getExclude() );
+
+ merged.getInclude().addAll( dto.getInclude() );
+ merged.getInclude().addAll( override.getInclude() );
+
+ return new PluginScanConfigImpl( merged );
+ }
+
+ @Nonnull
+ public ScanConfigProperties getTagScanProperties( String tagName )
+ {
+ ScanConfigProperties scanProperties = findTagScanProperties( tagName );
+ return scanProperties != null ? scanProperties : defaultScanConfig();
+ }
+
+ @Override
+ public DirScanConfigType dto()
+ {
+ return dto;
+ }
+
+ private ScanConfigProperties findTagScanProperties( String tagName )
+ {
+ ScanConfigProperties scanConfigProperties = findConfigByName( tagName, dto.getInclude() );
+ if ( scanConfigProperties == null )
+ {
+ scanConfigProperties = findConfigByName( tagName, dto.getTagScanConfig() );
+ }
+ return scanConfigProperties;
+ }
+
+ private ScanConfigProperties findConfigByName( String tagName, List configs )
+ {
+
+ if ( configs == null )
+ {
+ return null;
+ }
+
+ for ( TagScanConfigType config : configs )
+ {
+ if ( StringUtils.equals( tagName, config.getTagName() ) )
+ {
+ return new ScanConfigProperties( config.isRecursive(), config.getGlob() );
+ }
+ }
+ return null;
+ }
+
+ private static ScanConfigProperties defaultScanConfig()
+ {
+ return new ScanConfigProperties( true, null );
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/ProjectUtils.java b/maven-core/src/main/java/org/apache/maven/caching/ProjectUtils.java
new file mode 100644
index 000000000000..176556850377
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/ProjectUtils.java
@@ -0,0 +1,197 @@
+package org.apache.maven.caching;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.handler.ArtifactHandler;
+import org.apache.maven.caching.jaxb.BuildInfoType;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.lifecycle.internal.ProjectIndex;
+import org.apache.maven.lifecycle.internal.builder.BuilderCommon;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.plugin.MojoExecution;
+import org.apache.maven.project.MavenProject;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.apache.commons.lang3.StringUtils.removeStart;
+import static org.apache.commons.lang3.StringUtils.trim;
+import static org.apache.maven.artifact.Artifact.LATEST_VERSION;
+import static org.apache.maven.artifact.Artifact.SNAPSHOT_VERSION;
+
+/**
+ * ProjectUtils
+ */
+public class ProjectUtils
+{
+
+ private static final List PHASES = Lists.newArrayList(
+ //clean
+ "pre-clean", "clean", "post-clean",
+ // default
+ "validate", "initialize", "generate-sources", "process-sources", "generate-resources", "process-resources",
+ "compile", "process-classes", "generate-test-sources", "process-test-sources", "generate-test-resources",
+ "process-test-resources", "test-compile", "process-test-classes", "test", "prepare-package", "package",
+ "pre-integration-test", "integration-test", "post-integration-test", "verify", "install", "deploy",
+ //site
+ "pre-site", "site", "post-site", "site-deploy" );
+
+ /**
+ * @param phase
+ * @param other
+ * @return true if the given phase is later than the other in maven lifecycle. Example: isLaterPhase("install",
+ * "clean") returns true;
+ */
+ public static boolean isLaterPhase( String phase, String other )
+ {
+ checkArgument( PHASES.contains( phase ), "Unsupported phase: " + phase );
+ checkArgument( PHASES.contains( other ), "Unsupported phase: " + other );
+
+ return PHASES.indexOf( phase ) > PHASES.indexOf( other );
+ }
+
+ public static boolean isBuilding( Dependency dependency, ProjectIndex projectIndex )
+ {
+ final MavenProject key = new MavenProject();
+ key.setGroupId( dependency.getGroupId() );
+ key.setArtifactId( dependency.getArtifactId() );
+ key.setVersion( dependency.getVersion() );
+ return projectIndex.getProjects().containsKey( BuilderCommon.getKey( key ) );
+ }
+
+ public static boolean isPomPackaging( MavenProject project )
+ {
+ return project.getPackaging().equals( "pom" ) && !new File( getSrcDir( project ) ).exists();
+ }
+
+ public static boolean isPom( Artifact artifact )
+ {
+ return artifact.getType().equals( "pom" );
+ }
+
+ public static boolean isPom( Dependency dependency )
+ {
+ return dependency.getType().equals( "pom" );
+ }
+
+ public static boolean isSnapshot( String version )
+ {
+ return version.endsWith( SNAPSHOT_VERSION ) || version.endsWith( LATEST_VERSION );
+ }
+
+ public static String getTargetDir( MavenProject project )
+ {
+ return FilenameUtils.concat( project.getBasedir().getAbsolutePath(), "target" );
+ }
+
+ public static String getSrcDir( MavenProject project )
+ {
+ return FilenameUtils.concat( project.getBasedir().getAbsolutePath(), "src" );
+ }
+
+ public static String normalizedName( Artifact artifact )
+ {
+
+ if ( artifact.getFile() == null )
+ {
+ return null;
+ }
+
+ StringBuilder filename = new StringBuilder( artifact.getArtifactId() );
+
+ if ( artifact.hasClassifier() )
+ {
+ filename.append( "-" ).append( artifact.getClassifier() );
+ }
+
+ final ArtifactHandler artifactHandler = artifact.getArtifactHandler();
+ if ( artifactHandler != null && StringUtils.isNotBlank( artifactHandler.getExtension() ) )
+ {
+ filename.append( "." ).append( artifactHandler.getExtension() );
+ }
+ return filename.toString();
+ }
+
+ public static String mojoExecutionKey( MojoExecution mojo )
+ {
+ return StringUtils.join( Lists.newArrayList( StringUtils.defaultIfEmpty( mojo.getExecutionId(), "emptyExecId" ),
+ StringUtils.defaultIfEmpty( mojo.getGoal(), "emptyGoal" ),
+ StringUtils.defaultIfEmpty( mojo.getLifecyclePhase(), "emptyLifecyclePhase" ),
+ StringUtils.defaultIfEmpty( mojo.getArtifactId(), "emptyArtifactId" ),
+ StringUtils.defaultIfEmpty( mojo.getGroupId(), "emptyGroupId" ),
+ StringUtils.defaultIfEmpty( mojo.getVersion(), "emptyVersion" ) ), ":" );
+ }
+
+ public static String getMultimoduleRoot( MavenSession session )
+ {
+ return System.getProperty( "maven.multiModuleProjectDirectory", session.getExecutionRootDirectory() );
+ }
+
+ public static BuildInfoType.Scm readGitInfo( MavenSession session ) throws IOException
+ {
+ final BuildInfoType.Scm scmCandidate = new BuildInfoType.Scm();
+ final Path gitDir = Paths.get( getMultimoduleRoot( session ), ".git" );
+ if ( Files.isDirectory( gitDir ) )
+ {
+ final Path headFile = gitDir.resolve( "HEAD" );
+ if ( Files.exists( headFile ) )
+ {
+ String headRef = readFirstLine( headFile, "" );
+ if ( headRef.startsWith( "ref: " ) )
+ {
+ String branch = trim( removeStart( headRef, "ref: " ) );
+ scmCandidate.setSourceBranch( branch );
+ final Path refPath = gitDir.resolve( branch );
+ if ( Files.exists( refPath ) )
+ {
+ String revision = readFirstLine( refPath, "" );
+ scmCandidate.setRevision( trim( revision ) );
+ }
+ }
+ else
+ {
+ scmCandidate.setSourceBranch( headRef );
+ scmCandidate.setRevision( headRef );
+ }
+ }
+ }
+ return scmCandidate;
+ }
+
+
+ private static String readFirstLine( Path path, String defaultValue ) throws IOException
+ {
+ final List lines = Files.readAllLines( path, StandardCharsets.UTF_8 );
+ return Iterables.getFirst( lines, defaultValue );
+ }
+
+
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/RemoteArtifactsRepository.java b/maven-core/src/main/java/org/apache/maven/caching/RemoteArtifactsRepository.java
new file mode 100644
index 000000000000..7c118c405739
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/RemoteArtifactsRepository.java
@@ -0,0 +1,42 @@
+package org.apache.maven.caching;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import com.google.common.base.Optional;
+import org.apache.maven.caching.jaxb.ArtifactType;
+import org.apache.maven.caching.xml.BuildInfo;
+import org.apache.maven.project.MavenProject;
+
+import java.io.IOException;
+
+/**
+ * RemoteArtifactsRepository
+ */
+public interface RemoteArtifactsRepository extends ArtifactsRepository
+{
+
+ byte[] getArtifactContent( CacheContext context, ArtifactType artifact ) throws IOException;
+
+ byte[] getResourceContent( String resourceUrl, String logReference );
+
+ String getResourceUrl( CacheContext context, String filename );
+
+ Optional findBaselineBuild( MavenProject project );
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/RestoreStatus.java b/maven-core/src/main/java/org/apache/maven/caching/RestoreStatus.java
new file mode 100644
index 000000000000..6bf8fefc2832
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/RestoreStatus.java
@@ -0,0 +1,31 @@
+package org.apache.maven.caching;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * RestoreStatus
+ */
+public enum RestoreStatus
+{
+ EMPTY,
+ FAILURE,
+ PARTIAL,
+ SUCCESS
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/ScanConfigProperties.java b/maven-core/src/main/java/org/apache/maven/caching/ScanConfigProperties.java
new file mode 100644
index 000000000000..bc9622a59fa8
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/ScanConfigProperties.java
@@ -0,0 +1,45 @@
+package org.apache.maven.caching;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * ScanConfigProperties
+ */
+public class ScanConfigProperties
+{
+ private final boolean recursive;
+ private final String glob;
+
+ public ScanConfigProperties( boolean recursive, String glob )
+ {
+ this.recursive = recursive;
+ this.glob = glob;
+ }
+
+ public boolean isRecursive()
+ {
+ return recursive;
+ }
+
+ public String getGlob()
+ {
+ return glob;
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/ZipUtils.java b/maven-core/src/main/java/org/apache/maven/caching/ZipUtils.java
new file mode 100644
index 000000000000..9f15bb1d2460
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/ZipUtils.java
@@ -0,0 +1,116 @@
+package org.apache.maven.caching;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.io.IOUtils;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * ZipUtils
+ */
+public class ZipUtils
+{
+
+ private static final int BUFFER_SIZE = 4096;
+
+ public static InputStream zipFolder( final Path dir ) throws IOException
+ {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ try ( ZipOutputStream zipOutputStream = new ZipOutputStream( out ) )
+ {
+ processFolder( dir, zipOutputStream );
+ }
+ return new ByteArrayInputStream( out.toByteArray() );
+ }
+
+ private static void processFolder( final Path dir, final ZipOutputStream zipOutputStream ) throws IOException
+ {
+ Files.walkFileTree( dir, new SimpleFileVisitor()
+ {
+ @Override
+ public FileVisitResult visitFile( Path path, BasicFileAttributes basicFileAttributes ) throws IOException
+ {
+ final ZipEntry zipEntry = new ZipEntry( dir.relativize( path ).toString() );
+ zipOutputStream.putNextEntry( zipEntry );
+ try ( InputStream inputStream = Files.newInputStream( path ) )
+ {
+ IOUtils.copy( inputStream, zipOutputStream );
+ }
+ zipOutputStream.closeEntry();
+ return FileVisitResult.CONTINUE;
+ }
+ } );
+ }
+
+ public static void unzip( InputStream is, Path out ) throws IOException
+ {
+ try ( ZipInputStream zis = new ZipInputStream( is ) )
+ {
+
+ ZipEntry entry = zis.getNextEntry();
+
+ while ( entry != null )
+ {
+ File file = new File( out.toFile(), entry.getName() );
+ file.setLastModified( entry.getTime() );
+
+ if ( entry.isDirectory() )
+ {
+ file.mkdirs();
+ }
+ else
+ {
+ File parent = file.getParentFile();
+
+ if ( !parent.exists() )
+ {
+ parent.mkdirs();
+ }
+
+ try ( BufferedOutputStream bos = new BufferedOutputStream( new FileOutputStream( file ) ) )
+ {
+ byte[] buffer = new byte[BUFFER_SIZE];
+ int location;
+ while ( ( location = zis.read( buffer ) ) != -1 )
+ {
+ bos.write( buffer, 0, location );
+ }
+ }
+ }
+ entry = zis.getNextEntry();
+ }
+ }
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/checksum/DependencyNotResolvedException.java b/maven-core/src/main/java/org/apache/maven/caching/checksum/DependencyNotResolvedException.java
new file mode 100644
index 000000000000..bcc017b8caab
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/checksum/DependencyNotResolvedException.java
@@ -0,0 +1,31 @@
+package org.apache.maven.caching.checksum;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * DependencyNotResolvedException
+ */
+public class DependencyNotResolvedException extends RuntimeException
+{
+ public DependencyNotResolvedException( String message )
+ {
+ super( message );
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/checksum/DigestUtils.java b/maven-core/src/main/java/org/apache/maven/caching/checksum/DigestUtils.java
new file mode 100644
index 000000000000..e837da1f8587
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/checksum/DigestUtils.java
@@ -0,0 +1,189 @@
+package org.apache.maven.caching.checksum;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.maven.caching.hash.HashChecksum;
+import org.apache.maven.caching.jaxb.DigestItemType;
+import org.mozilla.universalchardet.UniversalDetector;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.commons.lang3.StringUtils.containsAny;
+import static org.apache.commons.lang3.StringUtils.equalsAny;
+import static org.apache.commons.lang3.StringUtils.startsWith;
+import static org.apache.commons.lang3.StringUtils.startsWithAny;
+
+/**
+ * DigestUtils
+ */
+public class DigestUtils
+{
+
+ private static final ThreadLocal ENCODING_DETECTOR = new ThreadLocal()
+ {
+ @Override
+ protected UniversalDetector initialValue()
+ {
+ return new UniversalDetector( null );
+ }
+ };
+
+
+ public static DigestItemType pom( HashChecksum checksum, String effectivePom )
+ {
+ return item( "pom", effectivePom, checksum.update( effectivePom.getBytes( UTF_8 ) ) );
+ }
+
+ public static DigestItemType file( HashChecksum checksum, Path basedir, Path file ) throws IOException
+ {
+ byte[] content = Files.readAllBytes( file );
+ DigestItemType item = item( "file", normalize( basedir, file ), checksum.update( content ) );
+ try
+ {
+ populateContentDetails( file, content, item );
+ }
+ catch ( IOException ignore )
+ {
+ System.out.println( "hello" );
+ }
+ return item;
+ }
+
+ private static void populateContentDetails( Path file, byte[] content, DigestItemType item ) throws IOException
+ {
+ String contentType = Files.probeContentType( file );
+ if ( contentType != null )
+ {
+ item.setContent( contentType );
+ }
+ final boolean binary = isBinary( contentType );
+ item.setIsText( isText( contentType ) ? "yes" : binary ? "no" : "unknown" );
+ if ( !binary )
+ { // probing application/ files as well though might be binary
+ UniversalDetector detector = ENCODING_DETECTOR.get();
+ detector.reset();
+ detector.handleData( content, 0, Math.min( content.length, 16 * 1024 ) );
+ detector.dataEnd();
+ String detectedCharset = detector.getDetectedCharset();
+ Charset charset = UTF_8;
+ if ( detectedCharset != null )
+ {
+ item.setCharset( detectedCharset );
+ charset = Charset.forName( detectedCharset );
+ }
+ CharBuffer charBuffer = charset.decode( ByteBuffer.wrap( content ) );
+ String lineSeparator = detectLineSeparator( charBuffer );
+ item.setEol( StringUtils.defaultString( lineSeparator, "unknown" ) );
+ }
+ }
+
+ // TODO add support for .gitattributes to statically configure file type before falling back to probe based content checks
+ private static boolean isText( String contentType )
+ {
+ return startsWith( contentType, "text/" )
+ || containsAny( contentType, "+json", "+xml" ) // common mime type suffixes
+ || equalsAny( contentType, // some common text types
+ "application/json",
+ "application/rtf",
+ "application/x-sh",
+ "application/xml",
+ "application/javascript",
+ "application/sql"
+ );
+ }
+
+ private static boolean isBinary( String contentType )
+ {
+ return startsWithAny( contentType, "image/", "audio/", "video/", "font/" )
+ || containsAny( contentType, "+zip", "+gzip" )
+ || equalsAny( contentType,
+ "application/octet-stream",
+ "application/java-archive",
+ "application/x-bzip",
+ "application/x-bzip2",
+ "application/zip",
+ "application/gzip",
+ "application/x-tar",
+ "application/msword",
+ "application/vnd.ms-excel",
+ "application/vnd.ms-powerpoint",
+ "application/pdf"
+ );
+ }
+
+ public static DigestItemType dependency( HashChecksum checksum, String key, String hash )
+ {
+ return item( "dependency", key, checksum.update( hash ) );
+ }
+
+ private static String normalize( Path basedirPath, Path file )
+ {
+ return FilenameUtils.separatorsToUnix( relativize( basedirPath, file ).toString() );
+ }
+
+ private static Path relativize( Path basedirPath, Path file )
+ {
+ try
+ {
+ return basedirPath.relativize( file );
+ }
+ catch ( Exception ignore )
+ {
+ return file;
+ }
+ }
+
+ private static DigestItemType item( String type, String reference, String hash )
+ {
+ final DigestItemType item = new DigestItemType();
+ item.setType( type );
+ item.setValue( reference );
+ item.setHash( hash );
+ return item;
+ }
+
+ private DigestUtils()
+ {
+ }
+
+ public static String detectLineSeparator( CharSequence text )
+ {
+ // first line break only
+ int index = StringUtils.indexOfAny( text, "\n\r" );
+ if ( index == -1 || index >= text.length() )
+ {
+ return null;
+ }
+ char ch = text.charAt( index );
+ if ( ch == '\r' )
+ {
+ return index + 1 < text.length() && text.charAt( index + 1 ) == '\n' ? "CRLF" : "CR";
+ }
+ return ch == '\n' ? "LF" : null;
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/checksum/KeyUtils.java b/maven-core/src/main/java/org/apache/maven/caching/checksum/KeyUtils.java
new file mode 100644
index 000000000000..04d9207cb0dc
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/checksum/KeyUtils.java
@@ -0,0 +1,64 @@
+package org.apache.maven.caching.checksum;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.caching.jaxb.ArtifactType;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.project.MavenProject;
+
+/**
+ * KeyUtils
+ */
+public class KeyUtils
+{
+
+ private static final String SEPARATOR = ":";
+
+ public static String getProjectKey( MavenProject project )
+ {
+ return StringUtils.joinWith( SEPARATOR, project.getGroupId(), project.getArtifactId(), project.getVersion() );
+ }
+
+ public static String getVersionlessProjectKey( MavenProject project )
+ {
+ return StringUtils.joinWith( SEPARATOR, project.getGroupId(), project.getArtifactId() );
+ }
+
+ public static String getVersionlessDependencyKey( Dependency dependency )
+ {
+ return StringUtils.joinWith( SEPARATOR, dependency.getGroupId(), dependency.getArtifactId() );
+ }
+
+ public static String getArtifactKey( ArtifactType artifact )
+ {
+ return artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getType()
+ + ( artifact.getClassifier() != null ? ":" + artifact.getClassifier() : "" )
+ + ":" + artifact.getVersion();
+ }
+
+ public static String getArtifactKey( Artifact artifact )
+ {
+ return artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getType()
+ + ( artifact.getClassifier() != null ? ":" + artifact.getClassifier() : "" )
+ + ":" + artifact.getVersion();
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/checksum/MavenProjectInput.java b/maven-core/src/main/java/org/apache/maven/caching/checksum/MavenProjectInput.java
new file mode 100644
index 000000000000..8e5873ce7dcd
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/checksum/MavenProjectInput.java
@@ -0,0 +1,950 @@
+package org.apache.maven.caching.checksum;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+import com.google.common.base.Optional;
+import com.google.common.collect.Iterables;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
+import org.apache.maven.artifact.resolver.ArtifactResolutionRequest;
+import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
+import org.apache.maven.caching.Clock;
+import org.apache.maven.caching.LocalArtifactsRepository;
+import org.apache.maven.caching.PluginScanConfig;
+import org.apache.maven.caching.ProjectUtils;
+import org.apache.maven.caching.RemoteArtifactsRepository;
+import org.apache.maven.caching.ScanConfigProperties;
+import org.apache.maven.caching.hash.HashAlgorithm;
+import org.apache.maven.caching.hash.HashChecksum;
+import org.apache.maven.caching.hash.HashFactory;
+import org.apache.maven.caching.jaxb.DigestItemType;
+import org.apache.maven.caching.jaxb.ProjectsInputInfoType;
+import org.apache.maven.caching.xml.BuildInfo;
+import org.apache.maven.caching.xml.CacheConfig;
+import org.apache.maven.caching.xml.DtoUtils;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.lifecycle.internal.ProjectIndex;
+import org.apache.maven.model.Build;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.model.Model;
+import org.apache.maven.model.Plugin;
+import org.apache.maven.model.PluginExecution;
+import org.apache.maven.model.PluginManagement;
+import org.apache.maven.model.Resource;
+import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.repository.RepositorySystem;
+import org.codehaus.plexus.logging.Logger;
+import org.codehaus.plexus.util.IOUtil;
+import org.codehaus.plexus.util.WriterFactory;
+import org.codehaus.plexus.util.xml.Xpp3Dom;
+
+import javax.annotation.Nonnull;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.Writer;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+import java.util.concurrent.ConcurrentMap;
+
+import static org.apache.commons.lang3.StringUtils.contains;
+import static org.apache.commons.lang3.StringUtils.defaultIfEmpty;
+import static org.apache.commons.lang3.StringUtils.equalsAnyIgnoreCase;
+import static org.apache.commons.lang3.StringUtils.isBlank;
+import static org.apache.commons.lang3.StringUtils.replaceEachRepeatedly;
+import static org.apache.commons.lang3.StringUtils.startsWithAny;
+import static org.apache.commons.lang3.StringUtils.stripToEmpty;
+import static org.apache.maven.caching.ProjectUtils.isBuilding;
+import static org.apache.maven.caching.ProjectUtils.isSnapshot;
+import static org.apache.maven.caching.jaxb.PathSetType.Include;
+
+/**
+ * MavenProjectInput
+ */
+public class MavenProjectInput
+{
+
+ /**
+ * Version ov hashing algorithm implementation. It is recommended to change to simplify remote cache maintenance
+ */
+ public static final String CACHE_IMPLMENTATION_VERSION = "v10";
+
+ /**
+ * property name to pass glob value. The glob to be used to list directory files in plugins scanning
+ */
+ private static final String CACHE_INPUT_GLOB_NAME = "remote.cache.input.glob";
+ /**
+ * default glob, bbsdk/abfx specific
+ */
+ public static final String DEFAULT_GLOB = "{*.java,*.groovy,*.yaml,*.svcd,*.proto,*assembly.xml,assembly"
+ + "*.xml,*logback.xml,*.vm,*.ini,*.jks,*.properties,*.sh,*.bat}";
+ /**
+ * property name prefix to pass input files with project properties. smth like remote.cache.input.1 will be
+ * accepted
+ */
+ private static final String CACHE_INPUT_NAME = "remote.cache.input";
+ /**
+ * property name prefix to exclude files from input. smth like remote.cache.exclude.1 should be set in project
+ * props
+ */
+ private static final String CACHE_EXCLUDE_NAME = "remote.cache.exclude";
+ /**
+ * Flag to control if we should check values from plugin configs as file system objects
+ */
+ private static final String CACHE_PROCESS_PLUGINS = "remote.cache.processPlugins";
+ private final Logger logger;
+ private final MavenProject project;
+ private final MavenSession session;
+ private final LocalArtifactsRepository localCache;
+ private final RemoteArtifactsRepository remoteCache;
+ private final RepositorySystem repoSystem;
+ private final ArtifactHandlerManager artifactHandlerManager;
+ private final CacheConfig config;
+ private final ConcurrentMap projectArtifactsByKey;
+ private final PathIgnoringCaseComparator fileComparator;
+ private final DependencyComparator dependencyComparator;
+ private final List filteredOutPaths;
+ private final Path baseDirPath;
+ private final String dirGlob;
+ private final boolean processPlugins;
+ private final ProjectIndex projectIndex;
+
+ @SuppressWarnings( "checkstyle:parameternumber" )
+ public MavenProjectInput( MavenProject project,
+ MavenSession session,
+ CacheConfig config,
+ ProjectIndex projectIndex,
+ ConcurrentMap artifactsByKey,
+ RepositorySystem repoSystem,
+ ArtifactHandlerManager artifactHandlerManager,
+ Logger logger,
+ LocalArtifactsRepository localCache,
+ RemoteArtifactsRepository remoteCache )
+ {
+ this.project = project;
+ this.session = session;
+ this.config = config;
+ this.projectIndex = projectIndex;
+ this.projectArtifactsByKey = artifactsByKey;
+ this.baseDirPath = project.getBasedir().toPath().toAbsolutePath();
+ this.repoSystem = repoSystem;
+ this.artifactHandlerManager = artifactHandlerManager;
+ this.logger = logger;
+ this.localCache = localCache;
+ this.remoteCache = remoteCache;
+ Properties properties = project.getProperties();
+ this.dirGlob = properties.getProperty( CACHE_INPUT_GLOB_NAME, config.getDefaultGlob() );
+ this.processPlugins = Boolean.parseBoolean(
+ properties.getProperty( CACHE_PROCESS_PLUGINS, config.isProcessPlugins() ) );
+
+ Build build = project.getBuild();
+ filteredOutPaths = new ArrayList<>( Arrays.asList( normalizedPath( build.getDirectory() ), // target by default
+ normalizedPath( build.getOutputDirectory() ), normalizedPath( build.getTestOutputDirectory() ) ) );
+
+ for ( String excludePath : config.getGlobalExcludePaths() )
+ {
+ filteredOutPaths.add( Paths.get( excludePath ) );
+ }
+
+ for ( String propertyName : properties.stringPropertyNames() )
+ {
+ if ( propertyName.startsWith( CACHE_EXCLUDE_NAME ) )
+ {
+ filteredOutPaths.add( Paths.get( properties.getProperty( propertyName ) ) );
+ }
+ }
+
+ this.fileComparator = new PathIgnoringCaseComparator();
+ this.dependencyComparator = new DependencyComparator();
+ }
+
+ public ProjectsInputInfoType calculateChecksum( HashFactory hashFactory ) throws IOException
+ {
+ long time = Clock.time();
+
+ final String effectivePom = getEffectivePom( project.getOriginalEffectiveModel() );
+ final SortedSet inputFiles = getInputFiles();
+ final SortedMap dependenciesChecksum = getMutableDependencies();
+
+ final long inputTime = Clock.elapsed( time );
+ time = Clock.time();
+
+ // hash items: effective pom + input files + dependencies
+ final int count = 1 + inputFiles.size() + dependenciesChecksum.size();
+ final List items = new ArrayList<>( count );
+ final HashChecksum checksum = hashFactory.createChecksum( count );
+
+ Optional baselineHolder = Optional.absent();
+ if ( config.isBaselineDiffEnabled() )
+ {
+ baselineHolder =
+ remoteCache.findBaselineBuild( project ).transform( b -> b.getDto().getProjectsInputInfo() );
+ }
+
+ DigestItemType effectivePomChecksum = DigestUtils.pom( checksum, effectivePom );
+ items.add( effectivePomChecksum );
+ final boolean compareWithBaseline = config.isBaselineDiffEnabled() && baselineHolder.isPresent();
+ if ( compareWithBaseline )
+ {
+ checkEffectivePomMatch( baselineHolder.get(), effectivePomChecksum );
+ }
+
+ boolean sourcesMatched = true;
+ for ( Path file : inputFiles )
+ {
+ DigestItemType fileDigest = DigestUtils.file( checksum, baseDirPath, file );
+ items.add( fileDigest );
+ if ( compareWithBaseline )
+ {
+ sourcesMatched &= checkItemMatchesBaseline( baselineHolder.get(), fileDigest );
+ }
+ }
+ if ( compareWithBaseline )
+ {
+ logInfo( "Source code: " + ( sourcesMatched ? "MATCHED" : "OUT OF DATE" ) );
+ }
+
+ boolean dependenciesMatched = true;
+ for ( Map.Entry entry : dependenciesChecksum.entrySet() )
+ {
+ DigestItemType dependencyDigest =
+ DigestUtils.dependency( checksum, entry.getKey(), entry.getValue().getHash() );
+ items.add( dependencyDigest );
+ if ( compareWithBaseline )
+ {
+ dependenciesMatched &= checkItemMatchesBaseline( baselineHolder.get(), dependencyDigest );
+ }
+ }
+
+ if ( compareWithBaseline )
+ {
+ logInfo( "Dependencies: " + ( dependenciesMatched ? "MATCHED" : "OUT OF DATE" ) );
+ }
+
+ final ProjectsInputInfoType projectsInputInfoType = new ProjectsInputInfoType();
+ projectsInputInfoType.setChecksum( checksum.digest() );
+ projectsInputInfoType.getItem().addAll( items );
+
+ final long checksumTime = Clock.elapsed( time );
+
+ if ( logger.isDebugEnabled() )
+ {
+ for ( DigestItemType item : projectsInputInfoType.getItem() )
+ {
+ logger.debug( "Hash calculated, item: " + item.getType() + ", hash: " + item.getHash() );
+ }
+ }
+ logInfo(
+ "Project inputs calculated in " + inputTime + " ms. " + hashFactory.getAlgorithm()
+ + " checksum [" + projectsInputInfoType.getChecksum() + "] calculated in "
+ + checksumTime + " ms." );
+ return projectsInputInfoType;
+ }
+
+ private void checkEffectivePomMatch( ProjectsInputInfoType baselineBuild, DigestItemType effectivePomChecksum )
+ {
+ Optional pomHolder = Optional.absent();
+ for ( DigestItemType it : baselineBuild.getItem() )
+ {
+ if ( it.getType().equals( "pom" ) )
+ {
+ pomHolder = Optional.of( it );
+ break;
+ }
+ }
+
+ if ( pomHolder.isPresent() )
+ {
+ DigestItemType pomItem = pomHolder.get();
+ final boolean matches = StringUtils.equals( pomItem.getHash(), effectivePomChecksum.getHash() );
+ if ( !matches )
+ {
+ logInfo(
+ "Mismatch in effective poms. Current: " + effectivePomChecksum.getHash() + ", remote: "
+ + pomItem.getHash() );
+ }
+ logInfo( "Effective pom: " + ( matches ? "MATCHED" : "OUT OF DATE" ) );
+ }
+ }
+
+ private boolean checkItemMatchesBaseline( ProjectsInputInfoType baselineBuild, DigestItemType fileDigest )
+ {
+ Optional baselineFileDigest = Optional.absent();
+ for ( DigestItemType it : baselineBuild.getItem() )
+ {
+ if ( it.getType().equals( fileDigest.getType() )
+ && fileDigest.getValue().equals( it.getValue().trim() ) )
+ {
+ baselineFileDigest = Optional.of( it );
+ break;
+ }
+ }
+
+ boolean matched = false;
+ if ( baselineFileDigest.isPresent() )
+ {
+ String hash = baselineFileDigest.get().getHash();
+ matched = StringUtils.equals( hash, fileDigest.getHash() );
+ if ( !matched )
+ {
+ logInfo(
+ "Mismatch in " + fileDigest.getType() + ": " + fileDigest.getValue() + ". Local hash: "
+ + fileDigest.getHash() + ", remote: " + hash );
+ }
+ }
+ else
+ {
+ logInfo(
+ "Mismatch in " + fileDigest.getType() + ": " + fileDigest.getValue()
+ + ". Not found in remote cache" );
+ }
+ return matched;
+ }
+
+ /**
+ * @param prototype effective model fully resolved by maven build. Do not pass here just parsed Model.
+ */
+ private String getEffectivePom( Model prototype ) throws IOException
+ {
+ // TODO validate status of the model - it should be in resolved state
+ Model toHash = new Model();
+
+ toHash.setGroupId( prototype.getGroupId() );
+ toHash.setArtifactId( prototype.getArtifactId() );
+ toHash.setVersion( prototype.getVersion() );
+ toHash.setModules( prototype.getModules() );
+
+ Collections.sort( prototype.getDependencies(), dependencyComparator );
+ toHash.setDependencies( prototype.getDependencies() );
+
+ PluginManagement pluginManagement = prototype.getBuild().getPluginManagement();
+ pluginManagement.setPlugins( normalizePlugins( prototype.getBuild().getPluginManagement().getPlugins() ) );
+
+ List plugins = normalizePlugins( prototype.getBuild().getPlugins() );
+
+ Build build = new Build();
+ build.setPluginManagement( pluginManagement );
+ build.setPlugins( plugins );
+
+ toHash.setBuild( build );
+
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+
+ Writer writer = null;
+ try
+ {
+ writer = WriterFactory.newXmlWriter( output );
+ new MavenXpp3Writer().write( writer, toHash );
+
+ //normalize env specifics
+ final String[] searchList = {baseDirPath.toString(), "\\", "windows", "linux"};
+ final String[] replacementList = {"", "/", "os.classifier", "os.classifier"};
+
+ return replaceEachRepeatedly( output.toString(), searchList, replacementList );
+
+ }
+ finally
+ {
+ IOUtil.close( writer );
+ }
+ }
+
+ private SortedSet getInputFiles()
+ {
+ long start = System.currentTimeMillis();
+ HashSet visitedDirs = new HashSet<>();
+ ArrayList collectedFiles = new ArrayList<>();
+
+ Build build = project.getBuild();
+
+ final boolean recursive = true;
+ startWalk( Paths.get( build.getSourceDirectory() ), dirGlob, recursive, collectedFiles, visitedDirs );
+ for ( Resource resource : build.getResources() )
+ {
+ startWalk( Paths.get( resource.getDirectory() ), dirGlob, recursive, collectedFiles, visitedDirs );
+ }
+
+ startWalk( Paths.get( build.getTestSourceDirectory() ), dirGlob, recursive, collectedFiles, visitedDirs );
+ for ( Resource testResource : build.getTestResources() )
+ {
+ startWalk( Paths.get( testResource.getDirectory() ), dirGlob, recursive, collectedFiles, visitedDirs );
+ }
+
+ Properties properties = project.getProperties();
+ for ( String name : properties.stringPropertyNames() )
+ {
+ if ( name.startsWith( CACHE_INPUT_NAME ) )
+ {
+ String path = properties.getProperty( name );
+ startWalk( Paths.get( path ), dirGlob, recursive, collectedFiles, visitedDirs );
+ }
+ }
+
+ List includes = config.getGlobalIncludePaths();
+ for ( Include include : includes )
+ {
+ final String path = include.getValue();
+ final String glob = defaultIfEmpty( include.getGlob(), dirGlob );
+ startWalk( Paths.get( path ), glob, include.isRecursive(), collectedFiles, visitedDirs );
+ }
+
+ long walkKnownPathsFinished = System.currentTimeMillis() - start;
+
+ String message = processPlugins ? "enabled, values will be checked for presence in file system" : "disabled, "
+ + "only tags with attribute " + CACHE_INPUT_NAME + "=\"true\" will be added";
+ logInfo( "Scanning plugins configurations to find input files. Probing is " + message );
+
+ if ( processPlugins )
+ {
+ collectFromPlugins( collectedFiles, visitedDirs );
+ }
+ else
+ {
+ logInfo( "Skipping check plugins scan (probing is disabled by config)" );
+ }
+
+ long pluginsFinished = System.currentTimeMillis() - start - walkKnownPathsFinished;
+
+ TreeSet sorted = new TreeSet<>( fileComparator );
+ for ( Path collectedFile : collectedFiles )
+ {
+ sorted.add( collectedFile.normalize().toAbsolutePath() );
+ }
+
+ logInfo(
+ "Found " + sorted.size() + " input files. Project dir processing: " + walkKnownPathsFinished
+ + ", plugins: " + pluginsFinished + " millis" );
+ if ( logger.isDebugEnabled() )
+ {
+ logDebug( "Src input: " + sorted );
+ }
+
+ return sorted;
+ }
+
+ /**
+ * entry point for directory walk
+ */
+ private void startWalk( Path candidate,
+ String glob,
+ boolean recursive,
+ List collectedFiles,
+ Set visitedDirs )
+ {
+
+ Path normalized = candidate.isAbsolute() ? candidate : baseDirPath.resolve( candidate );
+ normalized = normalized.toAbsolutePath().normalize();
+ WalkKey key = new WalkKey( normalized, glob, recursive );
+ if ( visitedDirs.contains( key ) || !Files.exists( normalized ) )
+ {
+ return;
+ }
+
+ if ( Files.isDirectory( normalized ) )
+ {
+ if ( baseDirPath.startsWith( normalized ) )
+ { // requested to walk parent, can do only non recursive
+ key = new WalkKey( normalized, glob, false );
+ }
+ try
+ {
+ walkDir( key, collectedFiles, visitedDirs );
+ visitedDirs.add( key );
+ }
+ catch ( IOException e )
+ {
+ throw new RuntimeException( e );
+ }
+ }
+ else
+ {
+ if ( !isFilteredOutSubpath( normalized ) )
+ {
+ if ( logger.isDebugEnabled() )
+ {
+ logDebug( "Adding: " + normalized );
+ }
+ collectedFiles.add( normalized );
+ }
+ }
+ }
+
+ private Path normalizedPath( String directory )
+ {
+ return Paths.get( directory ).normalize();
+ }
+
+ private void collectFromPlugins( List files, HashSet visitedDirs )
+ {
+
+ List plugins = project.getBuild().getPlugins();
+ for ( Plugin plugin : plugins )
+ {
+
+ PluginScanConfig scanConfig = config.getPluginDirScanConfig( plugin );
+
+ if ( scanConfig.isSkip() )
+ {
+ logDebug( "Skipping plugin config scan (skip by config): " + plugin.getArtifactId() );
+ continue;
+ }
+
+ Xpp3Dom configuration = (Xpp3Dom) plugin.getConfiguration();
+ logDebug( "Processing plugin config: " + plugin.getArtifactId() );
+ if ( configuration != null )
+ {
+ addInputsFromPluginConfigs( configuration.getChildren(), scanConfig, files, visitedDirs );
+ }
+
+ for ( PluginExecution exec : plugin.getExecutions() )
+ {
+
+ final PluginScanConfig executionScanConfig = config.getExecutionDirScanConfig( plugin, exec );
+ PluginScanConfig mergedConfig = scanConfig.mergeWith( executionScanConfig );
+
+ if ( mergedConfig.isSkip() )
+ {
+ logDebug(
+ "Skipping plugin execution config scan (skip by config): "
+ + plugin.getArtifactId() + ", execId: " + exec.getId() );
+ continue;
+ }
+
+ Xpp3Dom execConfiguration = (Xpp3Dom) exec.getConfiguration();
+ logDebug( "Processing plugin: " + plugin.getArtifactId() + ", execution: " + exec.getId() );
+
+ if ( execConfiguration != null )
+ {
+ addInputsFromPluginConfigs( execConfiguration.getChildren(), mergedConfig, files, visitedDirs );
+ }
+ }
+ }
+ }
+
+ private Path walkDir( final WalkKey key,
+ final List collectedFiles,
+ final Set visitedDirs ) throws IOException
+ {
+ return Files.walkFileTree( key.getPath(), new SimpleFileVisitor()
+ {
+ @Override
+ public FileVisitResult preVisitDirectory( Path path,
+ BasicFileAttributes basicFileAttributes ) throws IOException
+ {
+ WalkKey currentDirKey = new WalkKey( path.toAbsolutePath().normalize(), key.getGlob(),
+ key.isRecursive() );
+ if ( isHidden( path ) )
+ {
+ if ( logger.isDebugEnabled() )
+ {
+ logDebug( "Skipping subtree (hidden): " + path );
+ }
+ return FileVisitResult.SKIP_SUBTREE;
+ }
+ else if ( isFilteredOutSubpath( path ) )
+ {
+ if ( logger.isDebugEnabled() )
+ {
+ logDebug( "Skipping subtree (blacklisted): " + path );
+ }
+ return FileVisitResult.SKIP_SUBTREE;
+ }
+ else if ( visitedDirs.contains( currentDirKey ) )
+ {
+ if ( logger.isDebugEnabled() )
+ {
+ logDebug( "Skipping subtree (visited): " + path );
+ }
+ return FileVisitResult.SKIP_SUBTREE;
+ }
+
+ walkDirectoryFiles( path, collectedFiles, key.getGlob() );
+
+ if ( !key.isRecursive() )
+ {
+ if ( logger.isDebugEnabled() )
+ {
+ logDebug( "Skipping subtree (non recursive): " + path );
+ }
+ return FileVisitResult.SKIP_SUBTREE;
+ }
+
+ if ( logger.isDebugEnabled() )
+ {
+ logDebug( "Visiting subtree: " + path );
+ }
+ return FileVisitResult.CONTINUE;
+ }
+ } );
+ }
+
+ private void addInputsFromPluginConfigs( Xpp3Dom[] configurationChildren,
+ PluginScanConfig scanConfig,
+ List files, HashSet visitedDirs )
+ {
+
+ if ( configurationChildren == null )
+ {
+ return;
+ }
+
+ for ( Xpp3Dom configChild : configurationChildren )
+ {
+
+ String tagValue = configChild.getValue();
+ String tagName = configChild.getName();
+
+ if ( !scanConfig.accept( tagName ) )
+ {
+ logDebug( "Skipping property (scan config)): " + tagName + ", value: " + stripToEmpty( tagValue ) );
+ continue;
+ }
+
+ logDebug( "Checking xml tag. Tag: " + tagName + ", value: " + stripToEmpty( tagValue ) );
+
+ addInputsFromPluginConfigs( configChild.getChildren(), scanConfig, files, visitedDirs );
+
+ final ScanConfigProperties propertyConfig = scanConfig.getTagScanProperties( tagName );
+ final String glob = defaultIfEmpty( propertyConfig.getGlob(), dirGlob );
+ if ( "true".equals( configChild.getAttribute( CACHE_INPUT_NAME ) ) )
+ {
+ logInfo(
+ "Found tag marked with " + CACHE_INPUT_NAME + " attribute. Tag: " + tagName
+ + ", value: " + tagValue );
+ startWalk( Paths.get( tagValue ), glob, propertyConfig.isRecursive(), files, visitedDirs );
+ }
+ else
+ {
+ final Path candidate = getPathOrNull( tagValue );
+ if ( candidate != null )
+ {
+ startWalk( candidate, glob, propertyConfig.isRecursive(), files, visitedDirs );
+ if ( "descriptorRef".equals( tagName ) )
+ { // hardcoded logic for assembly plugin which could reference files omitting .xml suffix
+ startWalk( Paths.get( tagValue + ".xml" ), glob, propertyConfig.isRecursive(), files,
+ visitedDirs );
+ }
+ }
+ }
+ }
+ }
+
+ private Path getPathOrNull( String text )
+ {
+ // small optimization to not probe not-paths
+ boolean blacklisted = isBlank( text )
+ || equalsAnyIgnoreCase( text, "true", "false", "utf-8", "null", "\\" ) // common values
+ || contains( text, "*" ) // tag value is a glob or regex - unclear how to process
+ || ( contains( text, ":" ) && !contains( text, ":\\" ) )// artifactId
+ || startsWithAny( text, "com.", "org.", "io.", "java.", "javax." ) // java packages
+ || startsWithAny( text, "${env." ) // env variables in maven notation
+ || startsWithAny( text, "http:", "https:", "scm:", "ssh:", "git:", "svn:", "cp:",
+ "classpath:" ); // urls identified by common protocols
+ if ( !blacklisted )
+ {
+ try
+ {
+ return Paths.get( text );
+ }
+ catch ( Exception ignore )
+ {
+ }
+ }
+ if ( logger.isDebugEnabled() )
+ {
+ logDebug( text + ( blacklisted ? ": skipped(blacklisted literal)" : ": invalid path" ) );
+ }
+ return null;
+ }
+
+ private void logDebug( String message )
+ {
+ if ( logger.isDebugEnabled() )
+ {
+ logger.debug( "[CACHE][" + project.getArtifactId() + "] " + message );
+ }
+ }
+
+ private void logInfo( String message )
+ {
+ if ( logger.isInfoEnabled() )
+ {
+ logger.info( "[CACHE][" + project.getArtifactId() + "] " + message );
+ }
+ }
+
+ static void walkDirectoryFiles( Path dir, List collectedFiles, String glob )
+ {
+ if ( !Files.isDirectory( dir ) )
+ {
+ return;
+ }
+
+ try
+ {
+ try ( DirectoryStream stream = Files.newDirectoryStream( dir, glob ) )
+ {
+ for ( Path entry : stream )
+ {
+ File file = entry.toFile();
+ if ( file.isFile() && !isHidden( entry ) )
+ {
+ collectedFiles.add( entry );
+ }
+ }
+ }
+ }
+ catch ( IOException e )
+ {
+ throw new RuntimeException( "Cannot process directory: " + dir, e );
+ }
+ }
+
+ private static boolean isHidden( Path entry ) throws IOException
+ {
+ return Files.isHidden( entry ) || entry.toFile().getName().startsWith( "." );
+ }
+
+ private boolean isFilteredOutSubpath( Path path )
+ {
+ Path normalized = path.normalize();
+ for ( Path filteredOutDir : filteredOutPaths )
+ {
+ if ( normalized.startsWith( filteredOutDir ) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private SortedMap getMutableDependencies() throws IOException
+ {
+ MultimoduleDiscoveryStrategy strategy = config.getMultimoduleDiscoveryStrategy();
+ SortedMap result = new TreeMap<>();
+
+ for ( Dependency dependency : project.getDependencies() )
+ {
+ // saved to index by the end of dependency build
+ final boolean currentlyBuilding = isBuilding( dependency, projectIndex );
+ final boolean partOfMultiModule = strategy.isPartOfMultiModule( dependency );
+ if ( !currentlyBuilding && !partOfMultiModule && !isSnapshot( dependency.getVersion() ) )
+ {
+ // external immutable dependency, should skip
+ continue;
+ }
+
+ if ( ProjectUtils.isPom( dependency ) )
+ {
+ // POM dependency will be resolved by maven system to actual dependencies
+ // and will contribute to effective pom.
+ // Effective result will be recorded by #getNormalizedPom
+ // so pom dependencies must be skipped as meaningless by themselves
+ continue;
+ }
+
+ final Artifact dependencyArtifact = repoSystem.createDependencyArtifact( dependency );
+ final String artifactKey = KeyUtils.getArtifactKey( dependencyArtifact );
+ DigestItemType resolved = null;
+ if ( currentlyBuilding )
+ {
+ resolved = projectArtifactsByKey.get( artifactKey );
+ if ( resolved == null )
+ {
+ throw new DependencyNotResolvedException( "Expected dependency not resolved: " + dependency );
+ }
+ }
+ else
+ {
+ if ( partOfMultiModule )
+ {
+ // TODO lookup in remote cache is not necessary for abfx, for versioned projects - make sense
+ final Optional bestMatchResult = localCache.findBestMatchingBuild( session, dependency );
+ if ( bestMatchResult.isPresent() )
+ {
+ final BuildInfo bestMatched = bestMatchResult.get();
+ resolved = bestMatched.findArtifact( dependency );
+ }
+ }
+ if ( resolved == null && !ProjectUtils.isPom( dependency ) )
+ {
+ try
+ {
+ resolved = resolveArtifact( dependencyArtifact, strategy );
+ }
+ catch ( Exception e )
+ {
+ throw new RuntimeException( "Cannot resolve dependency " + dependency, e );
+ }
+ }
+ }
+ result.put( artifactKey, resolved );
+ }
+ return result;
+ }
+
+ @Nonnull
+ private DigestItemType resolveArtifact( final Artifact dependencyArtifact,
+ MultimoduleDiscoveryStrategy strategy ) throws IOException
+ {
+
+ ArtifactResolutionRequest request = new ArtifactResolutionRequest().setArtifact(
+ dependencyArtifact ).setResolveRoot( true ).setResolveTransitively( false ).setLocalRepository(
+ session.getLocalRepository() ).setRemoteRepositories(
+ project.getRemoteArtifactRepositories() ).setOffline(
+ session.isOffline() || !strategy.isLookupRemoteMavenRepo( dependencyArtifact ) ).setForceUpdate(
+ session.getRequest().isUpdateSnapshots() ).setServers( session.getRequest().getServers() ).setMirrors(
+ session.getRequest().getMirrors() ).setProxies( session.getRequest().getProxies() );
+
+ final ArtifactResolutionResult result = repoSystem.resolve( request );
+
+ if ( !result.isSuccess() )
+ {
+ throw new DependencyNotResolvedException( "Cannot resolve in-project dependency: " + dependencyArtifact );
+ }
+
+ if ( !result.getMissingArtifacts().isEmpty() )
+ {
+ throw new DependencyNotResolvedException(
+ "Cannot resolve artifact: " + dependencyArtifact + ", missing: " + result.getMissingArtifacts() );
+ }
+
+ if ( result.getArtifacts().size() > 1 )
+ {
+ throw new IllegalStateException(
+ "Unexpected number of artifacts returned. Requested: " + dependencyArtifact
+ + ", expected: 1, actual: " + result.getArtifacts() );
+ }
+
+ final Artifact resolved = Iterables.getOnlyElement( result.getArtifacts() );
+
+ final HashAlgorithm algorithm = config.getHashFactory().createAlgorithm();
+ final String hash = algorithm.hash( resolved.getFile().toPath() );
+ return DtoUtils.createDigestedFile( resolved, hash );
+ }
+
+ /**
+ * PathIgnoringCaseComparator
+ */
+ public static class PathIgnoringCaseComparator implements Comparator
+ {
+ @Override
+ public int compare( Path f1, Path f2 )
+ {
+ String s1 = f1.toAbsolutePath().toString();
+ String s2 = f2.toAbsolutePath().toString();
+ if ( File.separator.equals( "\\" ) )
+ {
+ s1 = s1.replaceAll( "\\\\", "/" );
+ s2 = s2.replaceAll( "\\\\", "/" );
+ }
+ return s1.compareToIgnoreCase( s2 );
+ }
+ }
+
+ /**
+ * DependencyComparator
+ */
+ public static class DependencyComparator implements Comparator
+ {
+ @Override
+ public int compare( Dependency d1, Dependency d2 )
+ {
+ return d1.getArtifactId().compareTo( d2.getArtifactId() );
+ }
+ }
+
+ private List normalizePlugins( List plugins )
+ {
+ for ( Plugin plugin : plugins )
+ {
+ List excludeProperties = config.getEffectivePomExcludeProperties( plugin );
+ removeBlacklistedAttributes( (Xpp3Dom) plugin.getConfiguration(), excludeProperties );
+ for ( PluginExecution execution : plugin.getExecutions() )
+ {
+ Xpp3Dom config = (Xpp3Dom) execution.getConfiguration();
+ removeBlacklistedAttributes( config, excludeProperties );
+ }
+ Collections.sort( plugin.getDependencies(), dependencyComparator );
+ }
+ return plugins;
+ }
+
+ private void removeBlacklistedAttributes( Xpp3Dom node, List excludeProperties )
+ {
+ if ( node == null )
+ {
+ return;
+ }
+
+ Xpp3Dom[] children = node.getChildren();
+ for ( int i = 0; i < children.length; i++ )
+ {
+ Xpp3Dom child = children[i];
+ if ( excludeProperties.contains( child.getName() ) )
+ {
+ node.removeChild( i );
+ continue;
+ }
+ removeBlacklistedAttributes( child, excludeProperties );
+ }
+ }
+
+ public Logger getLogger()
+ {
+ return logger;
+ }
+
+ public CacheConfig getConfig()
+ {
+ return config;
+ }
+
+ public MavenSession getSession()
+ {
+ return session;
+ }
+
+ public MavenProject getProject()
+ {
+ return project;
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/checksum/MultimoduleDiscoveryStrategy.java b/maven-core/src/main/java/org/apache/maven/caching/checksum/MultimoduleDiscoveryStrategy.java
new file mode 100644
index 000000000000..b968f7764dca
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/checksum/MultimoduleDiscoveryStrategy.java
@@ -0,0 +1,33 @@
+package org.apache.maven.caching.checksum;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.model.Dependency;
+
+/**
+ * MultimoduleDiscoveryStrategy
+ */
+public interface MultimoduleDiscoveryStrategy
+{
+ boolean isPartOfMultiModule( Dependency dependency );
+
+ boolean isLookupRemoteMavenRepo( Artifact dependency );
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/checksum/WalkKey.java b/maven-core/src/main/java/org/apache/maven/caching/checksum/WalkKey.java
new file mode 100644
index 000000000000..0d6f274df5b6
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/checksum/WalkKey.java
@@ -0,0 +1,95 @@
+package org.apache.maven.caching.checksum;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.nio.file.Path;
+
+/**
+ * WalkKey
+ */
+public class WalkKey
+{
+ private final Path normalized;
+ private final String glob;
+ private final boolean recursive;
+
+ public WalkKey( Path normalized, String glob, boolean recursive )
+ {
+
+ this.normalized = normalized;
+ this.glob = glob;
+ this.recursive = recursive;
+ }
+
+ @Override
+ public boolean equals( Object o )
+ {
+ if ( this == o )
+ {
+ return true;
+ }
+ if ( o == null || getClass() != o.getClass() )
+ {
+ return false;
+ }
+
+ WalkKey key = (WalkKey) o;
+
+ if ( recursive != key.recursive )
+ {
+ return false;
+ }
+ if ( !normalized.equals( key.normalized ) )
+ {
+ return false;
+ }
+ return glob.equals( key.glob );
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int result = normalized.hashCode();
+ result = 31 * result + glob.hashCode();
+ result = 31 * result + ( recursive ? 1 : 0 );
+ return result;
+ }
+
+ public Path getPath()
+ {
+ return normalized;
+ }
+
+ public String getGlob()
+ {
+ return glob;
+ }
+
+ public boolean isRecursive()
+ {
+ return recursive;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "WalkKey{" + "normalized=" + normalized + ", glob='" + glob + '\'' + ", recursive=" + recursive + '}';
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/hash/CloseableBuffer.java b/maven-core/src/main/java/org/apache/maven/caching/hash/CloseableBuffer.java
new file mode 100644
index 000000000000..0fec3646ffa4
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/hash/CloseableBuffer.java
@@ -0,0 +1,185 @@
+package org.apache.maven.caching.hash;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileChannel.MapMode;
+import java.security.PrivilegedAction;
+
+import static java.security.AccessController.doPrivileged;
+import static org.apache.maven.caching.hash.ReflectionUtils.getField;
+import static org.apache.maven.caching.hash.ReflectionUtils.getMethod;
+
+/**
+ * CloseableBuffer https://stackoverflow.com/a/54046774
+ */
+public class CloseableBuffer implements AutoCloseable
+{
+ /* Java 8
+ private static final Cleaner CLEANER = doPrivileged((PrivilegedAction) () -> {
+ final boolean isOldJava = System.getProperty("java.specification.version", "9").startsWith("1.");
+ if (isOldJava) {
+ return DirectCleaner.isSupported() ? new DirectCleaner() : new NoopCleaner();
+ } else {
+ return UnsafeCleaner.isSupported() ? new UnsafeCleaner() : new NoopCleaner();
+ }
+ });
+ */
+ private static final Cleaner CLEANER = doPrivileged( new PrivilegedAction()
+ {
+ @Override
+ public Cleaner run()
+ {
+ return DirectCleaner.isSupported() ? new DirectCleaner() : new NoopCleaner();
+ }
+ } );
+
+ public static CloseableBuffer directBuffer( int capacity )
+ {
+ return new CloseableBuffer( ByteBuffer.allocateDirect( capacity ) );
+ }
+
+ public static CloseableBuffer mappedBuffer( FileChannel channel, MapMode mode ) throws IOException
+ {
+ return new CloseableBuffer( channel.map( mode, 0, channel.size() ) );
+ }
+
+ private ByteBuffer buffer;
+
+ /**
+ * Unmap only DirectByteBuffer and MappedByteBuffer
+ */
+ private CloseableBuffer( ByteBuffer buffer )
+ {
+ // Java 8: buffer.isDirect()
+ this.buffer = buffer;
+ }
+
+ /**
+ * Do not use buffer after close
+ */
+ public ByteBuffer getBuffer()
+ {
+ return buffer;
+ }
+
+ @Override
+ public void close()
+ {
+ // Java 8: () -> CLEANER.clean(buffer)
+ boolean done = doPrivileged( new PrivilegedAction()
+ {
+ @Override
+ public Boolean run()
+ {
+ return CLEANER.clean( buffer );
+ }
+ } );
+ if ( done )
+ {
+ buffer = null;
+ }
+ }
+
+ // Java 8: @FunctionalInterface
+ private interface Cleaner
+ {
+ boolean clean( ByteBuffer buffer );
+ }
+
+ private static class NoopCleaner implements Cleaner
+ {
+ @Override
+ public boolean clean( ByteBuffer buffer )
+ {
+ return false;
+ }
+ }
+
+ private static class DirectCleaner implements Cleaner
+ {
+ private static final Method ATTACHMENT = getMethod( "sun.nio.ch.DirectBuffer",
+ "attachment" );
+ private static final Method CLEANER = getMethod( "sun.nio.ch.DirectBuffer", "cleaner" );
+ private static final Method CLEAN = getMethod( "sun.misc.Cleaner", "clean" );
+
+ public static boolean isSupported()
+ {
+ return ATTACHMENT != null && CLEAN != null && CLEANER != null;
+ }
+
+ /**
+ * Make sure duplicates and slices are not cleaned, since this can result in duplicate attempts to clean the
+ * same buffer, which trigger a crash with: "A fatal error has been detected by the Java Runtime Environment:
+ * EXCEPTION_ACCESS_VIOLATION" See: https://stackoverflow.com/a/31592947/3950982
+ */
+ @Override
+ public boolean clean( ByteBuffer buffer )
+ {
+ try
+ {
+ if ( ATTACHMENT.invoke( buffer ) == null )
+ {
+ CLEAN.invoke( CLEANER.invoke( buffer ) );
+ return true;
+ }
+ }
+ catch ( Exception ignore )
+ {
+ }
+ return false;
+ }
+ }
+
+ private static class UnsafeCleaner implements Cleaner
+ {
+ // Java 9: getMethod("jdk.internal.misc.Unsafe", "invokeCleaner", ByteBuffer.class);
+ private static final Method INVOKE_CLEANER = getMethod( "sun.misc.Unsafe", "invokeCleaner", ByteBuffer.class );
+ private static final Object UNSAFE = getField( "sun.misc.Unsafe", "theUnsafe" );
+
+ public static boolean isSupported()
+ {
+ return UNSAFE != null && INVOKE_CLEANER != null;
+ }
+
+ /**
+ * Calling the above code in JDK9+ gives a reflection warning on stderr,
+ * Unsafe.theUnsafe.invokeCleaner(byteBuffer)
+ * makes the same call, but does not print the reflection warning
+ */
+ @Override
+ public boolean clean( ByteBuffer buffer )
+ {
+ try
+ {
+ // throws IllegalArgumentException if buffer is a duplicate or slice
+ INVOKE_CLEANER.invoke( UNSAFE, buffer );
+ return true;
+ }
+ catch ( Exception ignore )
+ {
+ }
+ return false;
+ }
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/hash/Hash.java b/maven-core/src/main/java/org/apache/maven/caching/hash/Hash.java
new file mode 100644
index 000000000000..82220634cc6b
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/hash/Hash.java
@@ -0,0 +1,61 @@
+package org.apache.maven.caching.hash;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+/**
+ * Hash
+ */
+public class Hash
+{
+ /**
+ * Algorithm
+ */
+ public interface Algorithm
+ {
+ byte[] hash( byte[] array );
+
+ byte[] hash( Path path ) throws IOException;
+ }
+
+ /**
+ * accumulates states and should be completed by {@link #digest()}
+ */
+ public interface Checksum
+ {
+ void update( byte[] hash );
+
+ byte[] digest();
+ }
+
+ /**
+ * Factory
+ */
+ public interface Factory
+ {
+ String getAlgorithm();
+
+ Algorithm algorithm();
+
+ Checksum checksum( int count );
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/hash/HashAlgorithm.java b/maven-core/src/main/java/org/apache/maven/caching/hash/HashAlgorithm.java
new file mode 100644
index 000000000000..ec6ad68a053d
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/hash/HashAlgorithm.java
@@ -0,0 +1,46 @@
+package org.apache.maven.caching.hash;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+/**
+ * HashAlgorithm
+ */
+public class HashAlgorithm
+{
+ private final Hash.Algorithm algorithm;
+
+ HashAlgorithm( Hash.Algorithm algorithm )
+ {
+ this.algorithm = algorithm;
+ }
+
+ public String hash( Path path ) throws IOException
+ {
+ return HexUtils.encode( algorithm.hash( path ) );
+ }
+
+ public String hash( byte[] bytes )
+ {
+ return HexUtils.encode( algorithm.hash( bytes ) );
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/hash/HashChecksum.java b/maven-core/src/main/java/org/apache/maven/caching/hash/HashChecksum.java
new file mode 100644
index 000000000000..904077a6abae
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/hash/HashChecksum.java
@@ -0,0 +1,67 @@
+package org.apache.maven.caching.hash;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+/**
+ * HashChecksum
+ */
+public class HashChecksum
+{
+ private final Hash.Algorithm algorithm;
+ private final Hash.Checksum checksum;
+
+ HashChecksum( Hash.Algorithm algorithm, Hash.Checksum checksum )
+ {
+ this.algorithm = algorithm;
+ this.checksum = checksum;
+ }
+
+ public String update( Path path ) throws IOException
+ {
+ return updateAndEncode( algorithm.hash( path ) );
+ }
+
+ public String update( byte[] bytes )
+ {
+ return updateAndEncode( algorithm.hash( bytes ) );
+ }
+
+ /**
+ * @param hexHash hash value in hex format. This method doesn't accept generic text - could result in error
+ */
+ public String update( String hexHash )
+ {
+ return updateAndEncode( HexUtils.decode( hexHash ) );
+ }
+
+ private String updateAndEncode( byte[] hash )
+ {
+ checksum.update( hash );
+ return HexUtils.encode( hash );
+ }
+
+ public String digest()
+ {
+ return HexUtils.encode( checksum.digest() );
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/hash/HashFactory.java b/maven-core/src/main/java/org/apache/maven/caching/hash/HashFactory.java
new file mode 100644
index 000000000000..d610fceab924
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/hash/HashFactory.java
@@ -0,0 +1,79 @@
+package org.apache.maven.caching.hash;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * HashFactory
+ */
+public enum HashFactory
+{
+ SHA1( new SHA( "SHA-1" ) ),
+ SHA256( new SHA( "SHA-256" ) ),
+ SHA384( new SHA( "SHA-384" ) ),
+ SHA512( new SHA( "SHA-512" ) ),
+ XX( new XX() ),
+ XXMM( new XXMM() );
+
+ private static final Map LOOKUP = new HashMap<>();
+
+ static
+ {
+ for ( HashFactory factory : HashFactory.values() )
+ {
+ LOOKUP.put( factory.getAlgorithm(), factory );
+ }
+ }
+
+ public static HashFactory of( String algorithm ) throws NoSuchAlgorithmException
+ {
+ final HashFactory factory = LOOKUP.get( algorithm );
+ if ( factory == null )
+ {
+ throw new NoSuchAlgorithmException( algorithm );
+ }
+ return factory;
+ }
+
+ private final Hash.Factory factory;
+
+ HashFactory( Hash.Factory factory )
+ {
+ this.factory = factory;
+ }
+
+ public String getAlgorithm()
+ {
+ return factory.getAlgorithm();
+ }
+
+ public HashAlgorithm createAlgorithm()
+ {
+ return new HashAlgorithm( factory.algorithm() );
+ }
+
+ public HashChecksum createChecksum( int count )
+ {
+ return new HashChecksum( factory.algorithm(), factory.checksum( count ) );
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/hash/HexUtils.java b/maven-core/src/main/java/org/apache/maven/caching/hash/HexUtils.java
new file mode 100644
index 000000000000..f85d0f9c515c
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/hash/HexUtils.java
@@ -0,0 +1,56 @@
+package org.apache.maven.caching.hash;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import com.google.common.base.CharMatcher;
+import com.google.common.io.BaseEncoding;
+
+/**
+ * HexUtils
+ */
+public class HexUtils
+{
+
+ private static final BaseEncoding ENCODING = BaseEncoding.base16().lowerCase();
+ private static final CharMatcher MATCHER = CharMatcher.is( '0' );
+
+ public static String encode( byte[] hash )
+ {
+ return trimLeadingZero( ENCODING.encode( hash ) );
+ }
+
+ public static byte[] decode( String hex )
+ {
+ return ENCODING.decode( padLeadingZero( hex ) );
+ }
+
+ private static String trimLeadingZero( String hex )
+ {
+ String value = MATCHER.trimLeadingFrom( hex );
+ return value.isEmpty() ? "0" : value;
+ }
+
+ private static String padLeadingZero( String hex )
+ {
+ String value = hex.toLowerCase();
+ return value.length() % 2 == 0 ? value : "0" + value;
+ }
+
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/hash/ReflectionUtils.java b/maven-core/src/main/java/org/apache/maven/caching/hash/ReflectionUtils.java
new file mode 100644
index 000000000000..259e6f0b7feb
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/hash/ReflectionUtils.java
@@ -0,0 +1,61 @@
+package org.apache.maven.caching.hash;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+/**
+ * ReflectionUtils
+ */
+class ReflectionUtils
+{
+ static Method getMethod( String className, String methodName, Class>... parameterTypes )
+ {
+ try
+ {
+ final Method method = Class.forName( className ).getMethod( methodName, parameterTypes );
+ method.setAccessible( true );
+ return method;
+ }
+ catch ( Exception ignore )
+ {
+ return null;
+ }
+ }
+
+ static Object getField( String className, String fieldName )
+ {
+ try
+ {
+ final Field field = Class.forName( className ).getDeclaredField( fieldName );
+ field.setAccessible( true );
+ return field.get( null );
+ }
+ catch ( Exception ignore )
+ {
+ return null;
+ }
+ }
+
+ private ReflectionUtils()
+ {
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/hash/SHA.java b/maven-core/src/main/java/org/apache/maven/caching/hash/SHA.java
new file mode 100644
index 000000000000..e02dfe2f25e2
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/hash/SHA.java
@@ -0,0 +1,103 @@
+package org.apache.maven.caching.hash;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.MessageDigest;
+
+/**
+ * SHA
+ */
+public class SHA implements Hash.Factory
+{
+ private static final ThreadLocal ALGORITHM = new ThreadLocal<>();
+ private static final ThreadLocal CHECKSUM = new ThreadLocal<>();
+
+ private final String algorithm;
+
+ SHA( String algorithm )
+ {
+ this.algorithm = algorithm;
+ }
+
+ @Override
+ public String getAlgorithm()
+ {
+ return algorithm;
+ }
+
+ @Override
+ public Hash.Algorithm algorithm()
+ {
+ return new SHA.Algorithm( ThreadLocalDigest.get( ALGORITHM, algorithm ) );
+ }
+
+ @Override
+ public Hash.Checksum checksum( int count )
+ {
+ return new SHA.Checksum( ThreadLocalDigest.get( CHECKSUM, algorithm ) );
+ }
+
+ private static class Algorithm implements Hash.Algorithm
+ {
+ private final MessageDigest digest;
+
+ private Algorithm( MessageDigest digest )
+ {
+ this.digest = digest;
+ }
+
+ @Override
+ public byte[] hash( byte[] array )
+ {
+ return digest.digest( array );
+ }
+
+ @Override
+ public byte[] hash( Path path ) throws IOException
+ {
+ return hash( Files.readAllBytes( path ) );
+ }
+ }
+
+ private static class Checksum implements Hash.Checksum
+ {
+ private final MessageDigest digest;
+
+ private Checksum( MessageDigest digest )
+ {
+ this.digest = digest;
+ }
+
+ @Override
+ public void update( byte[] hash )
+ {
+ digest.update( hash );
+ }
+
+ @Override
+ public byte[] digest()
+ {
+ return digest.digest();
+ }
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/hash/ThreadLocalBuffer.java b/maven-core/src/main/java/org/apache/maven/caching/hash/ThreadLocalBuffer.java
new file mode 100644
index 000000000000..6d9f9d6269b7
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/hash/ThreadLocalBuffer.java
@@ -0,0 +1,86 @@
+package org.apache.maven.caching.hash;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * ThreadLocalBuffer
+ */
+public class ThreadLocalBuffer
+{
+ private static final ConcurrentMap LOCALS = new ConcurrentHashMap<>();
+
+ public static ByteBuffer get( ThreadLocal local, int capacity )
+ {
+ final CloseableBuffer buffer = local.get();
+ if ( buffer == null )
+ {
+ return create( local, capacity );
+ }
+
+ if ( capacity( buffer ) < capacity )
+ {
+ close( buffer );
+ return create( local, capacity * 2 );
+ }
+
+ return clear( buffer );
+ }
+
+ @Override
+ public void finalize()
+ {
+ for ( CloseableBuffer buffer : LOCALS.keySet() )
+ {
+ buffer.close();
+ }
+ }
+
+ private static ByteBuffer create( ThreadLocal local, int capacity )
+ {
+ final CloseableBuffer buffer = CloseableBuffer.directBuffer( capacity );
+ local.set( buffer );
+ LOCALS.put( buffer, false );
+ return buffer.getBuffer();
+ }
+
+ private static int capacity( CloseableBuffer buffer )
+ {
+ return buffer.getBuffer().capacity();
+ }
+
+ private static ByteBuffer clear( CloseableBuffer buffer )
+ {
+ return (ByteBuffer) buffer.getBuffer().clear();
+ }
+
+ private static void close( CloseableBuffer buffer )
+ {
+ LOCALS.remove( buffer );
+ buffer.close();
+ }
+
+ private ThreadLocalBuffer()
+ {
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/hash/ThreadLocalDigest.java b/maven-core/src/main/java/org/apache/maven/caching/hash/ThreadLocalDigest.java
new file mode 100644
index 000000000000..2df800e26f7a
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/hash/ThreadLocalDigest.java
@@ -0,0 +1,71 @@
+package org.apache.maven.caching.hash;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Objects;
+
+/**
+ * ThreadLocalDigest
+ */
+public class ThreadLocalDigest
+{
+ public static MessageDigest get( ThreadLocal local, String algorithm )
+ {
+ final MessageDigest digest = local.get();
+ if ( digest == null )
+ {
+ return create( local, algorithm );
+ }
+
+ if ( Objects.equals( digest.getAlgorithm(), algorithm ) )
+ {
+ return reset( digest );
+ }
+
+ reset( digest );
+ return create( local, algorithm );
+ }
+
+ private static MessageDigest create( ThreadLocal local, String algorithm )
+ {
+ try
+ {
+ final MessageDigest digest = MessageDigest.getInstance( algorithm );
+ local.set( digest );
+ return digest;
+ }
+ catch ( NoSuchAlgorithmException e )
+ {
+ throw new RuntimeException( "Cannot create message digest with algorithm: " + algorithm, e );
+ }
+ }
+
+ private static MessageDigest reset( MessageDigest digest )
+ {
+ digest.reset();
+ return digest;
+ }
+
+ private ThreadLocalDigest()
+ {
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/hash/XX.java b/maven-core/src/main/java/org/apache/maven/caching/hash/XX.java
new file mode 100644
index 000000000000..24f9f4bff5e7
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/hash/XX.java
@@ -0,0 +1,98 @@
+package org.apache.maven.caching.hash;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import net.openhft.hashing.LongHashFunction;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import static com.google.common.primitives.Longs.toByteArray;
+
+/**
+ * XX
+ */
+public class XX implements Hash.Factory
+{
+ static final LongHashFunction INSTANCE = LongHashFunction.xx();
+
+ @Override
+ public String getAlgorithm()
+ {
+ return "XX";
+ }
+
+ @Override
+ public Hash.Algorithm algorithm()
+ {
+ return new XX.Algorithm();
+ }
+
+ @Override
+ public Hash.Checksum checksum( int count )
+ {
+ return new XX.Checksum( ByteBuffer.allocate( capacity( count ) ) );
+ }
+
+ static int capacity( int count )
+ {
+ // Java 8: Long.BYTES
+ return count * Long.SIZE / Byte.SIZE;
+ }
+
+ static class Algorithm implements Hash.Algorithm
+ {
+ @Override
+ public byte[] hash( byte[] array )
+ {
+ return toByteArray( INSTANCE.hashBytes( array ) );
+ }
+
+ @Override
+ public byte[] hash( Path path ) throws IOException
+ {
+ return hash( Files.readAllBytes( path ) );
+ }
+ }
+
+ static class Checksum implements Hash.Checksum
+ {
+ private final ByteBuffer buffer;
+
+ Checksum( ByteBuffer buffer )
+ {
+ this.buffer = buffer;
+ }
+
+ @Override
+ public void update( byte[] hash )
+ {
+ buffer.put( hash );
+ }
+
+ @Override
+ public byte[] digest()
+ {
+ return toByteArray( INSTANCE.hashBytes( buffer, 0, buffer.position() ) );
+ }
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/hash/XXMM.java b/maven-core/src/main/java/org/apache/maven/caching/hash/XXMM.java
new file mode 100644
index 000000000000..8e09299c247e
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/hash/XXMM.java
@@ -0,0 +1,67 @@
+package org.apache.maven.caching.hash;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.nio.channels.FileChannel;
+import java.nio.file.Path;
+
+import static com.google.common.primitives.Longs.toByteArray;
+import static java.nio.channels.FileChannel.MapMode.READ_ONLY;
+import static java.nio.file.StandardOpenOption.READ;
+
+/**
+ * XXMM
+ */
+public class XXMM implements Hash.Factory
+{
+ private static final ThreadLocal BUFFER = new ThreadLocal<>();
+
+ @Override
+ public String getAlgorithm()
+ {
+ return "XXMM";
+ }
+
+ @Override
+ public Hash.Algorithm algorithm()
+ {
+ return new Algorithm();
+ }
+
+ @Override
+ public Hash.Checksum checksum( int count )
+ {
+ return new XX.Checksum( ThreadLocalBuffer.get( BUFFER, XX.capacity( count ) ) );
+ }
+
+ private static class Algorithm extends XX.Algorithm
+ {
+ @Override
+ public byte[] hash( Path path ) throws IOException
+ {
+ try ( FileChannel channel = FileChannel.open( path,
+ READ ); CloseableBuffer buffer = CloseableBuffer.mappedBuffer( channel, READ_ONLY ) )
+ {
+ return toByteArray( XX.INSTANCE.hashBytes( buffer.getBuffer() ) );
+ }
+ }
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/xml/AllExternalSrategy.java b/maven-core/src/main/java/org/apache/maven/caching/xml/AllExternalSrategy.java
new file mode 100644
index 000000000000..00c1d4d84ac8
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/xml/AllExternalSrategy.java
@@ -0,0 +1,42 @@
+package org.apache.maven.caching.xml;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.caching.checksum.MultimoduleDiscoveryStrategy;
+import org.apache.maven.model.Dependency;
+
+/**
+ * AllExternalSrategy
+ */
+public class AllExternalSrategy implements MultimoduleDiscoveryStrategy
+{
+ @Override
+ public boolean isPartOfMultiModule( Dependency dependency )
+ {
+ return false;
+ }
+
+ @Override
+ public boolean isLookupRemoteMavenRepo( Artifact artifact )
+ {
+ return true;
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/xml/BuildInfo.java b/maven-core/src/main/java/org/apache/maven/caching/xml/BuildInfo.java
new file mode 100644
index 000000000000..0b61bfd89d74
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/xml/BuildInfo.java
@@ -0,0 +1,273 @@
+package org.apache.maven.caching.xml;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import com.google.common.collect.Iterables;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.caching.ProjectUtils;
+import org.apache.maven.caching.checksum.MavenProjectInput;
+import org.apache.maven.caching.hash.HashAlgorithm;
+import org.apache.maven.caching.jaxb.ArtifactType;
+import org.apache.maven.caching.jaxb.BuildInfoType;
+import org.apache.maven.caching.jaxb.CompletedExecutionType;
+import org.apache.maven.caching.jaxb.DigestItemType;
+import org.apache.maven.caching.jaxb.ProjectsInputInfoType;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.plugin.MojoExecution;
+import org.codehaus.plexus.logging.Logger;
+import org.codehaus.plexus.util.StringUtils;
+
+import javax.xml.datatype.DatatypeConfigurationException;
+import javax.xml.datatype.DatatypeFactory;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.GregorianCalendar;
+import java.util.List;
+import java.util.Objects;
+
+import static org.apache.maven.caching.ProjectUtils.isLaterPhase;
+import static org.apache.maven.caching.ProjectUtils.mojoExecutionKey;
+
+/**
+ * BuildInfo
+ */
+public class BuildInfo
+{
+
+ final BuildInfoType dto;
+ CacheSource source;
+
+ public BuildInfo( List goals,
+ ArtifactType artifact,
+ List attachedArtifacts,
+ ProjectsInputInfoType projectsInputInfo,
+ List completedExecutions,
+ String hashAlgorithm )
+ {
+ this.dto = new BuildInfoType();
+ this.dto.setCacheImplementationVersion( MavenProjectInput.CACHE_IMPLMENTATION_VERSION );
+ try
+ {
+ this.dto.setBuildTime( DatatypeFactory.newInstance().newXMLGregorianCalendar( new GregorianCalendar() ) );
+ }
+ catch ( DatatypeConfigurationException ignore )
+ {
+ }
+ try
+ {
+ this.dto.setBuildServer( InetAddress.getLocalHost().getCanonicalHostName() );
+ }
+ catch ( UnknownHostException ignore )
+ {
+ this.dto.setBuildServer( "unknown" );
+ }
+ this.dto.setHashFunction( hashAlgorithm );
+ this.dto.setArtifact( artifact );
+ this.dto.setGoals( createGoals( goals ) );
+ this.dto.setAttachedArtifacts( new BuildInfoType.AttachedArtifacts() );
+ this.dto.getAttachedArtifacts().getArtifact().addAll( attachedArtifacts );
+ this.dto.setExecutions( createExecutions( completedExecutions ) );
+ this.dto.setProjectsInputInfo( projectsInputInfo );
+ this.source = CacheSource.BUILD;
+ }
+
+ public CacheSource getSource()
+ {
+ return source;
+ }
+
+ private BuildInfoType.Executions createExecutions( List completedExecutions )
+ {
+ BuildInfoType.Executions executions = new BuildInfoType.Executions();
+ executions.getExecution().addAll( completedExecutions );
+ return executions;
+ }
+
+ public BuildInfo( BuildInfoType buildInfo, CacheSource source )
+ {
+ this.dto = buildInfo;
+ this.source = source;
+ }
+
+ public static BuildInfoType.Goals createGoals( List list )
+ {
+ BuildInfoType.Goals goals = new BuildInfoType.Goals();
+ goals.getGoal().addAll( list );
+ return goals;
+ }
+
+ public static BuildInfoType.AttachedArtifacts createAttachedArtifacts( List artifacts,
+ HashAlgorithm algorithm ) throws IOException
+ {
+ BuildInfoType.AttachedArtifacts attachedArtifacts = new BuildInfoType.AttachedArtifacts();
+ for ( Artifact artifact : artifacts )
+ {
+ final ArtifactType dto = DtoUtils.createDto( artifact );
+ if ( artifact.getFile() != null )
+ {
+ dto.setFileHash( algorithm.hash( artifact.getFile().toPath() ) );
+ }
+ attachedArtifacts.getArtifact().add( dto );
+ }
+ return attachedArtifacts;
+ }
+
+ public boolean isAllExecutionsPresent( List mojos, Logger logger )
+ {
+ for ( MojoExecution mojo : mojos )
+ {
+ // TODO for strict check we might want exact match
+ if ( !hasCompletedExecution( mojoExecutionKey( mojo ) ) )
+ {
+ logger.error( "Build mojo is not cached: " + mojo );
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean hasCompletedExecution( String mojoExecutionKey )
+ {
+ final List completedExecutions = dto.getExecutions().getExecution();
+ for ( CompletedExecutionType completedExecution : completedExecutions )
+ {
+ if ( StringUtils.equals( completedExecution.getExecutionKey(), mojoExecutionKey ) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "BuildInfo{" + "dto=" + dto + '}';
+ }
+
+ public CompletedExecutionType findMojoExecutionInfo( MojoExecution mojoExecution )
+ {
+
+ if ( !dto.isSetExecutions() )
+ {
+ return null;
+ }
+
+ final List executions = dto.getExecutions().getExecution();
+ for ( CompletedExecutionType execution : executions )
+ {
+ if ( StringUtils.equals( execution.getExecutionKey(), mojoExecutionKey( mojoExecution ) ) )
+ {
+ return execution;
+ }
+ }
+ return null;
+ }
+
+ public String getCacheImplementationVersion()
+ {
+ return dto.getCacheImplementationVersion();
+ }
+
+ public ArtifactType getArtifact()
+ {
+ return dto.getArtifact();
+ }
+
+ public List getAttachedArtifacts()
+ {
+ if ( dto.isSetAttachedArtifacts() )
+ {
+ return dto.getAttachedArtifacts().getArtifact();
+ }
+ return Collections.emptyList();
+ }
+
+ public BuildInfoType getDto()
+ {
+ return dto;
+ }
+
+ public String getHighestCompletedGoal()
+ {
+ return Iterables.getLast( dto.getGoals().getGoal() );
+ }
+
+ public List getCachedSegment( List mojoExecutions )
+ {
+ List list = new ArrayList<>();
+ for ( MojoExecution mojoExecution : mojoExecutions )
+ {
+ if ( !isLaterPhase( mojoExecution.getLifecyclePhase(), "post-clean" ) )
+ {
+ continue;
+ }
+ if ( isLaterPhase( mojoExecution.getLifecyclePhase(), getHighestCompletedGoal() ) )
+ {
+ break;
+ }
+ list.add( mojoExecution );
+ }
+ return list;
+
+ }
+
+ public List getPostCachedSegment( List mojoExecutions )
+ {
+ List list = new ArrayList<>();
+ for ( MojoExecution mojoExecution : mojoExecutions )
+ {
+ if ( isLaterPhase( mojoExecution.getLifecyclePhase(), getHighestCompletedGoal() ) )
+ {
+ list.add( mojoExecution );
+ }
+ }
+ return list;
+ }
+
+ public DigestItemType findArtifact( Dependency dependency )
+ {
+
+ if ( ProjectUtils.isPom( dependency ) )
+ {
+ throw new IllegalArgumentException( "Pom dependencies should not be treated as artifacts: " + dependency );
+ }
+ List artifacts = new ArrayList<>( getAttachedArtifacts() );
+ artifacts.add( getArtifact() );
+ for ( ArtifactType artifact : artifacts )
+ {
+ if ( isEquals( dependency, artifact ) )
+ {
+ return DtoUtils.createdDigestedByProjectChecksum( artifact, dto.getProjectsInputInfo().getChecksum() );
+ }
+ }
+ return null;
+ }
+
+ private boolean isEquals( Dependency dependency, ArtifactType artifact )
+ {
+ return Objects.equals( dependency.getGroupId(), artifact.getArtifactId() ) && Objects.equals(
+ dependency.getArtifactId(), artifact.getArtifactId() ) && Objects.equals( dependency.getType(),
+ artifact.getType() ) && Objects.equals( dependency.getClassifier(), artifact.getClassifier() );
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/xml/CacheConfig.java b/maven-core/src/main/java/org/apache/maven/caching/xml/CacheConfig.java
new file mode 100644
index 000000000000..2f5137d9a3ea
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/xml/CacheConfig.java
@@ -0,0 +1,108 @@
+package org.apache.maven.caching.xml;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.caching.PluginScanConfig;
+import org.apache.maven.caching.checksum.MultimoduleDiscoveryStrategy;
+import org.apache.maven.caching.hash.HashFactory;
+import org.apache.maven.caching.jaxb.PathSetType;
+import org.apache.maven.caching.jaxb.PropertyNameType;
+import org.apache.maven.caching.jaxb.TrackedPropertyType;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.model.Plugin;
+import org.apache.maven.model.PluginExecution;
+import org.apache.maven.plugin.MojoExecution;
+import org.apache.maven.project.MavenProject;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * CacheConfig
+ */
+public interface CacheConfig
+{
+
+ CacheState initialize( MavenProject project, MavenSession session );
+
+ @Nonnull
+ List getTrackedProperties( MojoExecution mojoExecution );
+
+ boolean isLogAllProperties( MojoExecution mojoExecution );
+
+ @Nonnull
+ List getLoggedProperties( MojoExecution mojoExecution );
+
+ @Nonnull
+ List getNologProperties( MojoExecution mojoExecution );
+
+ @Nonnull
+ List getEffectivePomExcludeProperties( Plugin plugin );
+
+ String isProcessPlugins();
+
+ String getDefaultGlob();
+
+ @Nonnull
+ List getGlobalIncludePaths();
+
+ @Nonnull
+ List getGlobalExcludePaths();
+
+ @Nonnull
+ PluginScanConfig getPluginDirScanConfig( Plugin plugin );
+
+ @Nonnull
+ PluginScanConfig getExecutionDirScanConfig( Plugin plugin, PluginExecution exec );
+
+ @Nonnull
+ MultimoduleDiscoveryStrategy getMultimoduleDiscoveryStrategy();
+
+ @Nonnull
+ HashFactory getHashFactory();
+
+ boolean isForcedExecution( MojoExecution execution );
+
+ String getUrl();
+
+ boolean isEnabled();
+
+ boolean isRemoteCacheEnabled();
+
+ boolean isSaveToRemote();
+
+ boolean isSaveFinal();
+
+ boolean isFailFast();
+
+ int getMaxLocalBuildsCached();
+
+ List getAttachedOutputs();
+
+ boolean canIgnore( MojoExecution mojoExecution );
+
+ @Nonnull
+ List getExcludePatterns();
+
+ boolean isBaselineDiffEnabled();
+
+ String getBaselineCacheUrl();
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/xml/CacheConfigImpl.java b/maven-core/src/main/java/org/apache/maven/caching/xml/CacheConfigImpl.java
new file mode 100644
index 000000000000..9dd0e85f578f
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/xml/CacheConfigImpl.java
@@ -0,0 +1,579 @@
+package org.apache.maven.caching.xml;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.maven.caching.DefaultPluginScanConfig;
+import org.apache.maven.caching.PluginScanConfig;
+import org.apache.maven.caching.PluginScanConfigImpl;
+import org.apache.maven.caching.checksum.MultimoduleDiscoveryStrategy;
+import org.apache.maven.caching.hash.HashFactory;
+import org.apache.maven.caching.jaxb.CacheType;
+import org.apache.maven.caching.jaxb.ConfigurationType;
+import org.apache.maven.caching.jaxb.CoordinatesBaseType;
+import org.apache.maven.caching.jaxb.ExecutablesType;
+import org.apache.maven.caching.jaxb.ExecutionConfigurationScanType;
+import org.apache.maven.caching.jaxb.ExecutionControlType;
+import org.apache.maven.caching.jaxb.ExecutionIdsListType;
+import org.apache.maven.caching.jaxb.GoalReconciliationType;
+import org.apache.maven.caching.jaxb.GoalsListType;
+import org.apache.maven.caching.jaxb.PathSetType;
+import org.apache.maven.caching.jaxb.PluginConfigurationScanType;
+import org.apache.maven.caching.jaxb.PluginSetType;
+import org.apache.maven.caching.jaxb.PropertyNameType;
+import org.apache.maven.caching.jaxb.TrackedPropertyType;
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.model.Plugin;
+import org.apache.maven.model.PluginExecution;
+import org.apache.maven.plugin.MojoExecution;
+import org.apache.maven.project.MavenProject;
+import org.codehaus.plexus.component.annotations.Component;
+import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.logging.Logger;
+
+import javax.annotation.Nonnull;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import static com.google.common.base.Preconditions.checkState;
+import static java.lang.Boolean.TRUE;
+import static java.lang.Boolean.parseBoolean;
+import static org.apache.maven.caching.ProjectUtils.getMultimoduleRoot;
+
+/**
+ * CacheConfigImpl
+ */
+@Component( role = CacheConfig.class,
+ instantiationStrategy = "singleton" )
+public class CacheConfigImpl implements CacheConfig
+{
+
+ public static final String CONFIG_PATH_PROPERTY_NAME = "remote.cache.configPath";
+ public static final String CACHE_ENABLED_PROPERTY_NAME = "remote.cache.enabled";
+ public static final String SAVE_TO_REMOTE_PROPERTY_NAME = "remote.cache.save.enabled";
+ public static final String SAVE_NON_OVERRIDEABLE_NAME = "remote.cache.save.final";
+ public static final String FAIL_FAST_PROPERTY_NAME = "remote.cache.failFast";
+ public static final String BASELINE_BUILD_URL_PROPERTY_NAME = "remote.cache.baselineUrl";
+
+ @Requirement
+ private Logger logger;
+
+ @Requirement
+ private XmlService xmlService;
+
+ private volatile CacheState state = CacheState.NOT_INITIALIZED;
+ private volatile CacheType cacheConfig;
+ private volatile HashFactory hashFactory;
+
+ private final Supplier> excludePatterns = Suppliers.memoize( new Supplier>()
+ {
+ @Override
+ public List get()
+ {
+ return compileExcludePatterns();
+ }
+ } );
+
+
+ @Override
+ public synchronized CacheState initialize( MavenProject project, MavenSession session )
+ {
+
+ if ( state != CacheState.NOT_INITIALIZED )
+ {
+ return state;
+ }
+
+ final String enabled = System.getProperty( CACHE_ENABLED_PROPERTY_NAME, "true" );
+ if ( !parseBoolean( enabled ) )
+ {
+ logger.info( "Cache disabled by command line flag, project will be built fully and not cached" );
+ state = CacheState.DISABLED;
+ return state;
+ }
+
+ Path configPath = null;
+
+ String configPathText = System.getProperty( CONFIG_PATH_PROPERTY_NAME );
+ if ( StringUtils.isNotBlank( configPathText ) )
+ {
+ configPath = Paths.get( configPathText );
+ }
+ if ( configPath == null )
+ {
+ configPathText = project.getProperties().getProperty( CONFIG_PATH_PROPERTY_NAME );
+ if ( StringUtils.isNotBlank( configPathText ) )
+ {
+ configPath = Paths.get( configPathText );
+ }
+ }
+
+ if ( configPath == null )
+ {
+ configPath = Paths.get( getMultimoduleRoot( session ), ".mvn", "maven-cache-config.xml" );
+ }
+
+ if ( !Files.exists( configPath ) )
+ {
+ logger.warn(
+ "Cache configuration is not available at configured path " + configPath + ", cache is disabled" );
+ state = CacheState.DISABLED;
+ return state;
+ }
+
+ try
+ {
+ logger.info( "Loading cache configuration from " + configPath );
+ cacheConfig = xmlService.fromFile( CacheType.class, configPath.toFile() );
+ }
+ catch ( Exception e )
+ {
+ throw new IllegalArgumentException(
+ "Cannot initialize cache because xml config is not valid or not available", e );
+ }
+
+ if ( !cacheConfig.getConfiguration().isEnabled() )
+ {
+ state = CacheState.DISABLED;
+ return state;
+ }
+
+ String hashAlgorithm = null;
+ try
+ {
+ hashAlgorithm = getConfiguration().getHashAlgorithm();
+ hashFactory = HashFactory.of( hashAlgorithm );
+ logger.info( "Using " + hashAlgorithm + " hash algorithm for cache" );
+ }
+ catch ( Exception e )
+ {
+ throw new IllegalArgumentException( "Unsupported hashing algorithm: " + hashAlgorithm, e );
+ }
+
+ state = CacheState.INITIALIZED;
+ return state;
+ }
+
+ @Nonnull
+ @Override
+ public List getTrackedProperties( MojoExecution mojoExecution )
+ {
+ checkInitializedState();
+ final GoalReconciliationType reconciliationConfig = findReconciliationConfig( mojoExecution );
+ if ( reconciliationConfig != null )
+ {
+ return reconciliationConfig.getReconcile();
+ }
+ else
+ {
+ return Collections.emptyList();
+ }
+ }
+
+ @Override
+ public boolean isLogAllProperties( MojoExecution mojoExecution )
+ {
+ final GoalReconciliationType reconciliationConfig = findReconciliationConfig( mojoExecution );
+ if ( reconciliationConfig != null && reconciliationConfig.isLogAll() )
+ {
+ return true;
+ }
+ return cacheConfig.isSetExecutionControl() && cacheConfig.getExecutionControl().isSetReconcile()
+ && cacheConfig.getExecutionControl().getReconcile().isLogAllProperties();
+ }
+
+ private GoalReconciliationType findReconciliationConfig( MojoExecution mojoExecution )
+ {
+
+ if ( !cacheConfig.isSetExecutionControl() )
+ {
+ return null;
+ }
+
+ final ExecutionControlType executionControl = cacheConfig.getExecutionControl();
+ if ( !executionControl.isSetReconcile() )
+ {
+ return null;
+ }
+
+ final List reconciliation = executionControl.getReconcile().getPlugin();
+
+ for ( GoalReconciliationType goalReconciliationConfig : reconciliation )
+ {
+
+ final String goal = mojoExecution.getGoal();
+
+ if ( isPluginMatch( mojoExecution.getPlugin(), goalReconciliationConfig ) && StringUtils.equals( goal,
+ goalReconciliationConfig.getGoal() ) )
+ {
+ return goalReconciliationConfig;
+ }
+ }
+ return null;
+ }
+
+ @Nonnull
+ @Override
+ public List getLoggedProperties( MojoExecution mojoExecution )
+ {
+ checkInitializedState();
+
+ final GoalReconciliationType reconciliationConfig = findReconciliationConfig( mojoExecution );
+ if ( reconciliationConfig != null )
+ {
+ return reconciliationConfig.getLog();
+ }
+ else
+ {
+ return Collections.emptyList();
+ }
+ }
+
+ @Nonnull
+ @Override
+ public List getNologProperties( MojoExecution mojoExecution )
+ {
+ checkInitializedState();
+ final GoalReconciliationType reconciliationConfig = findReconciliationConfig( mojoExecution );
+ if ( reconciliationConfig != null )
+ {
+ return reconciliationConfig.getNolog();
+ }
+ else
+ {
+ return Collections.emptyList();
+ }
+ }
+
+ @Nonnull
+ @Override
+ public List getEffectivePomExcludeProperties( Plugin plugin )
+ {
+ checkInitializedState();
+ final PluginConfigurationScanType pluginConfig = findPluginScanConfig( plugin );
+
+ if ( pluginConfig != null && pluginConfig.isSetEffectivePom() )
+ {
+ return pluginConfig.getEffectivePom().getExcludeProperty();
+ }
+ return Collections.emptyList();
+ }
+
+ private PluginConfigurationScanType findPluginScanConfig( Plugin plugin )
+ {
+
+ if ( !cacheConfig.isSetInput() )
+ {
+ return null;
+ }
+
+ final List pluginConfigs = cacheConfig.getInput().getPlugin();
+ for ( PluginConfigurationScanType pluginConfig : pluginConfigs )
+ {
+ if ( isPluginMatch( plugin, pluginConfig ) )
+ {
+ return pluginConfig;
+ }
+ }
+ return null;
+ }
+
+ private boolean isPluginMatch( Plugin plugin, CoordinatesBaseType pluginConfig )
+ {
+ return StringUtils.equals( pluginConfig.getArtifactId(),
+ plugin.getArtifactId() ) && ( !pluginConfig.isSetGroupId() || StringUtils.equals(
+ pluginConfig.getGroupId(), plugin.getGroupId() ) );
+ }
+
+
+ @Nonnull
+ @Override
+ public PluginScanConfig getPluginDirScanConfig( Plugin plugin )
+ {
+ checkInitializedState();
+ final PluginConfigurationScanType pluginConfig = findPluginScanConfig( plugin );
+ if ( pluginConfig == null || !pluginConfig.isSetDirScan() )
+ {
+ return new DefaultPluginScanConfig();
+ }
+
+ return new PluginScanConfigImpl( pluginConfig.getDirScan() );
+ }
+
+ @Nonnull
+ @Override
+ public PluginScanConfig getExecutionDirScanConfig( Plugin plugin, PluginExecution exec )
+ {
+ checkInitializedState();
+ final PluginConfigurationScanType pluginScanConfig = findPluginScanConfig( plugin );
+
+ if ( pluginScanConfig != null )
+ {
+ final ExecutionConfigurationScanType executionScanConfig = findExecutionScanConfig( exec,
+ pluginScanConfig.getExecution() );
+ if ( executionScanConfig != null && executionScanConfig.isSetDirScan() )
+ {
+ return new PluginScanConfigImpl( executionScanConfig.getDirScan() );
+ }
+ }
+
+ return new DefaultPluginScanConfig();
+ }
+
+ private ExecutionConfigurationScanType findExecutionScanConfig( PluginExecution execution,
+ List scanConfigs )
+ {
+ for ( ExecutionConfigurationScanType executionScanConfig : scanConfigs )
+ {
+ if ( executionScanConfig.getExecId().contains( execution.getId() ) )
+ {
+ return executionScanConfig;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String isProcessPlugins()
+ {
+ checkInitializedState();
+ return TRUE.toString();
+ }
+
+ @Override
+ public String getDefaultGlob()
+ {
+ checkInitializedState();
+ return StringUtils.trim( cacheConfig.getInput().getGlobal().getGlob() );
+ }
+
+ @Nonnull
+ @Override
+ public List getGlobalIncludePaths()
+ {
+ checkInitializedState();
+ return cacheConfig.getInput().getGlobal().getInclude();
+ }
+
+ @Nonnull
+ @Override
+ public List getGlobalExcludePaths()
+ {
+ checkInitializedState();
+ return cacheConfig.getInput().getGlobal().getExclude();
+ }
+
+ @Nonnull
+ @Override
+ public MultimoduleDiscoveryStrategy getMultimoduleDiscoveryStrategy()
+ {
+ checkInitializedState();
+ final ConfigurationType.ProjectDiscoveryStrategy projectDiscoveryStrategy =
+ cacheConfig.getConfiguration().getProjectDiscoveryStrategy();
+ if ( projectDiscoveryStrategy.isSetSpecificVersion() )
+ {
+ return new SentinelVersionStartegy( projectDiscoveryStrategy.getSpecificVersion() );
+ }
+ return new AllExternalSrategy();
+ }
+
+ @Nonnull
+ @Override
+ public HashFactory getHashFactory()
+ {
+ checkInitializedState();
+ return hashFactory;
+ }
+
+ @Override
+ public boolean canIgnore( MojoExecution mojoExecution )
+ {
+ checkInitializedState();
+ if ( !cacheConfig.isSetExecutionControl() || !cacheConfig.getExecutionControl().isSetIgnoreMissing() )
+ {
+ return false;
+ }
+
+ return executionMatches( mojoExecution, cacheConfig.getExecutionControl().getIgnoreMissing() );
+ }
+
+ @Override
+ public boolean isForcedExecution( MojoExecution execution )
+ {
+ checkInitializedState();
+ if ( !cacheConfig.isSetExecutionControl() || !cacheConfig.getExecutionControl().isSetRunAlways() )
+ {
+ return false;
+ }
+
+ return executionMatches( execution, cacheConfig.getExecutionControl().getRunAlways() );
+ }
+
+ private boolean executionMatches( MojoExecution execution, ExecutablesType executablesType )
+ {
+ final List pluginConfigs = executablesType.getPlugin();
+ for ( PluginSetType pluginConfig : pluginConfigs )
+ {
+ if ( isPluginMatch( execution.getPlugin(), pluginConfig ) )
+ {
+ return true;
+ }
+ }
+
+ final List executionIds = executablesType.getExecution();
+ for ( ExecutionIdsListType executionConfig : executionIds )
+ {
+ if ( isPluginMatch( execution.getPlugin(), executionConfig ) && executionConfig.getExecId().contains(
+ execution.getExecutionId() ) )
+ {
+ return true;
+ }
+ }
+
+ final List pluginsGoalsList = executablesType.getGoals();
+ for ( GoalsListType pluginGoals : pluginsGoalsList )
+ {
+ if ( isPluginMatch( execution.getPlugin(), pluginGoals ) && pluginGoals.getGoal().contains(
+ execution.getGoal() ) )
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean isEnabled()
+ {
+ return state == CacheState.INITIALIZED;
+ }
+
+
+ @Override
+ public boolean isRemoteCacheEnabled()
+ {
+ checkInitializedState();
+ return getRemote().isEnabled();
+ }
+
+ @Override
+ public boolean isSaveToRemote()
+ {
+ checkInitializedState();
+ return Boolean.getBoolean( SAVE_TO_REMOTE_PROPERTY_NAME ) || getRemote().isSaveToRemote();
+ }
+
+ @Override
+ public boolean isSaveFinal()
+ {
+ return Boolean.getBoolean( SAVE_NON_OVERRIDEABLE_NAME );
+ }
+
+ @Override
+ public boolean isFailFast()
+ {
+ return Boolean.getBoolean( FAIL_FAST_PROPERTY_NAME );
+ }
+
+ @Override
+ public boolean isBaselineDiffEnabled()
+ {
+ return System.getProperties().containsKey( BASELINE_BUILD_URL_PROPERTY_NAME );
+ }
+
+ @Override
+ public String getBaselineCacheUrl()
+ {
+ return System.getProperty( BASELINE_BUILD_URL_PROPERTY_NAME );
+ }
+
+ @Override
+ public String getUrl()
+ {
+ checkInitializedState();
+ return getRemote().getUrl();
+ }
+
+
+ @Override
+ public int getMaxLocalBuildsCached()
+ {
+ checkInitializedState();
+ return getLocal().getMaxBuildsCached().intValue();
+ }
+
+ @Override
+ public List getAttachedOutputs()
+ {
+ checkInitializedState();
+ final ConfigurationType.AttachedOutputs attachedOutputs = getConfiguration().getAttachedOutputs();
+ return attachedOutputs == null ? Collections.emptyList() : attachedOutputs.getDirName();
+ }
+
+ @Nonnull
+ @Override
+ public List getExcludePatterns()
+ {
+ checkInitializedState();
+ return excludePatterns.get();
+ }
+
+ private List compileExcludePatterns()
+ {
+ if ( cacheConfig.isSetOutput() && cacheConfig.getOutput().isSetExclude() )
+ {
+ List patterns = new ArrayList<>();
+ for ( String pattern : cacheConfig.getOutput().getExclude().getPattern() )
+ {
+ patterns.add( Pattern.compile( pattern ) );
+ }
+ return patterns;
+ }
+ return Collections.emptyList();
+ }
+
+ private ConfigurationType.Remote getRemote()
+ {
+ return getConfiguration().getRemote();
+ }
+
+ private ConfigurationType.Local getLocal()
+ {
+ return getConfiguration().getLocal();
+ }
+
+ private ConfigurationType getConfiguration()
+ {
+ return cacheConfig.getConfiguration();
+ }
+
+ private void checkInitializedState()
+ {
+ checkState( state == CacheState.INITIALIZED, "Cache is not initialized. Actual state: " + state );
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/xml/CacheSource.java b/maven-core/src/main/java/org/apache/maven/caching/xml/CacheSource.java
new file mode 100644
index 000000000000..b21944463632
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/xml/CacheSource.java
@@ -0,0 +1,30 @@
+package org.apache.maven.caching.xml;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * CacheSource
+ */
+public enum CacheSource
+{
+ LOCAL,
+ REMOTE,
+ BUILD
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/xml/CacheState.java b/maven-core/src/main/java/org/apache/maven/caching/xml/CacheState.java
new file mode 100644
index 000000000000..99267ed01e89
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/xml/CacheState.java
@@ -0,0 +1,30 @@
+package org.apache.maven.caching.xml;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * CacheState
+ */
+public enum CacheState
+{
+ NOT_INITIALIZED,
+ DISABLED,
+ INITIALIZED
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/xml/DtoUtils.java b/maven-core/src/main/java/org/apache/maven/caching/xml/DtoUtils.java
new file mode 100644
index 000000000000..a2810ab86824
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/xml/DtoUtils.java
@@ -0,0 +1,219 @@
+package org.apache.maven.caching.xml;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.caching.ProjectUtils;
+import org.apache.maven.caching.jaxb.ArtifactType;
+import org.apache.maven.caching.jaxb.CompletedExecutionType;
+import org.apache.maven.caching.jaxb.DigestItemType;
+import org.apache.maven.caching.jaxb.PropertyValueType;
+import org.apache.maven.caching.jaxb.TrackedPropertyType;
+import org.apache.maven.model.Dependency;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+
+import static org.apache.maven.caching.checksum.KeyUtils.getArtifactKey;
+
+/**
+ * DtoUtils
+ */
+public class DtoUtils
+{
+
+ public static String findPropertyValue( String propertyName, CompletedExecutionType completedExecution )
+ {
+ final CompletedExecutionType.Configuration configuration = completedExecution.getConfiguration();
+ if ( configuration == null )
+ {
+ return null;
+ }
+ final List properties = configuration.getProperty();
+ for ( PropertyValueType property : properties )
+ {
+ if ( StringUtils.equals( propertyName, property.getName() ) )
+ {
+ return property.getValue();
+ }
+ }
+ return null;
+ }
+
+ public static ArtifactType createDto( Artifact artifact )
+ {
+ ArtifactType dto = new ArtifactType();
+ dto.setArtifactId( artifact.getArtifactId() );
+ dto.setGroupId( artifact.getGroupId() );
+ dto.setVersion( artifact.getVersion() );
+ dto.setClassifier( artifact.getClassifier() );
+ dto.setType( artifact.getType() );
+ dto.setScope( artifact.getScope() );
+ dto.setFileName( ProjectUtils.normalizedName( artifact ) );
+ return dto;
+ }
+
+ public static DigestItemType createdDigestedByProjectChecksum( ArtifactType artifact, String projectChecksum )
+ {
+ DigestItemType dit = new DigestItemType();
+ dit.setType( "module" );
+ dit.setHash( projectChecksum );
+ dit.setFileChecksum( artifact.getFileHash() );
+ dit.setValue( getArtifactKey( artifact ) );
+ return dit;
+ }
+
+ public static DigestItemType createDigestedFile( Artifact artifact, String fileHash )
+ {
+ DigestItemType dit = new DigestItemType();
+ dit.setType( "artifact" );
+ dit.setHash( fileHash );
+ dit.setFileChecksum( fileHash );
+ dit.setValue( getArtifactKey( artifact ) );
+ return dit;
+ }
+
+ public static Dependency createDependency( ArtifactType artifact )
+ {
+ final Dependency dependency = new Dependency();
+ dependency.setArtifactId( artifact.getArtifactId() );
+ dependency.setGroupId( artifact.getGroupId() );
+ dependency.setVersion( artifact.getVersion() );
+ dependency.setClassifier( artifact.getClassifier() );
+ dependency.setType( artifact.getType() );
+ dependency.setScope( artifact.getScope() );
+ return dependency;
+ }
+
+ public static Dependency createDependency( Artifact artifact )
+ {
+ final Dependency dependency = new Dependency();
+ dependency.setArtifactId( artifact.getArtifactId() );
+ dependency.setGroupId( artifact.getGroupId() );
+ dependency.setVersion( artifact.getVersion() );
+ dependency.setType( artifact.getType() );
+ dependency.setScope( artifact.getScope() );
+ dependency.setClassifier( artifact.getClassifier() );
+ return dependency;
+ }
+
+ public static void addProperty( CompletedExecutionType execution,
+ String propertyName,
+ Object value,
+ String baseDirPath,
+ boolean tracked )
+ {
+ if ( execution.getConfiguration() == null )
+ {
+ execution.setConfiguration( new CompletedExecutionType.Configuration() );
+ }
+ final PropertyValueType valueType = new PropertyValueType();
+ valueType.setName( propertyName );
+ if ( value != null && value.getClass().isArray() )
+ {
+ value = ArrayUtils.toString( value );
+ }
+ final String valueText = String.valueOf( value );
+ valueType.setValue( StringUtils.remove( valueText, baseDirPath ) );
+ valueType.setTracked( tracked );
+ execution.getConfiguration().getProperty().add( valueType );
+ }
+
+ public static boolean containsAllProperties(
+ @Nonnull CompletedExecutionType cachedExecution, List trackedProperties )
+ {
+
+ if ( trackedProperties == null || trackedProperties.isEmpty() )
+ {
+ return true;
+ }
+
+ if ( !cachedExecution.isSetConfiguration() )
+ {
+ return false;
+ }
+
+ final List executionProperties = cachedExecution.getConfiguration().getProperty();
+ for ( TrackedPropertyType trackedProperty : trackedProperties )
+ {
+ if ( !contains( executionProperties, trackedProperty.getPropertyName() ) )
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static boolean contains( List executionProperties, String propertyName )
+ {
+ for ( PropertyValueType executionProperty : executionProperties )
+ {
+ if ( StringUtils.equals( executionProperty.getName(), propertyName ) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static ArtifactType copy( ArtifactType artifact )
+ {
+ ArtifactType copy = new ArtifactType();
+ if ( artifact.isSetArtifactId() )
+ {
+ copy.setArtifactId( artifact.getArtifactId() );
+ }
+ if ( artifact.isSetGroupId() )
+ {
+ copy.setGroupId( artifact.getGroupId() );
+ }
+ if ( artifact.isSetVersion() )
+ {
+ copy.setVersion( artifact.getVersion() );
+ }
+ if ( artifact.isSetType() )
+ {
+ copy.setType( artifact.getType() );
+ }
+ if ( artifact.isSetClassifier() )
+ {
+ copy.setClassifier( artifact.getClassifier() );
+ }
+ if ( artifact.isSetScope() )
+ {
+ copy.setScope( artifact.getScope() );
+ }
+ if ( artifact.isSetFileName() )
+ {
+ copy.setFileName( artifact.getFileName() );
+ }
+ if ( artifact.isSetFileHash() )
+ {
+ copy.setFileHash( artifact.getFileHash() );
+ }
+ if ( artifact.isSetFileSize() )
+ {
+ copy.setFileSize( artifact.getFileSize() );
+ }
+ return copy;
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/xml/SentinelVersionStartegy.java b/maven-core/src/main/java/org/apache/maven/caching/xml/SentinelVersionStartegy.java
new file mode 100644
index 000000000000..6d332308cc07
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/xml/SentinelVersionStartegy.java
@@ -0,0 +1,51 @@
+package org.apache.maven.caching.xml;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.caching.checksum.MultimoduleDiscoveryStrategy;
+import org.apache.maven.model.Dependency;
+import org.codehaus.plexus.util.StringUtils;
+
+/**
+ * SentinelVersionStartegy
+ */
+public class SentinelVersionStartegy implements MultimoduleDiscoveryStrategy
+{
+ private final String version;
+
+ public SentinelVersionStartegy( String version )
+ {
+ this.version = version;
+ }
+
+ @Override
+ public boolean isPartOfMultiModule( Dependency dependency )
+ {
+ return StringUtils.equals( version, dependency.getVersion() );
+ }
+
+ @Override
+ public boolean isLookupRemoteMavenRepo( Artifact artifact )
+ {
+ // TODO abfx specific - should be config driven
+ return !StringUtils.equals( version, artifact.getVersion() );
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/caching/xml/XmlService.java b/maven-core/src/main/java/org/apache/maven/caching/xml/XmlService.java
new file mode 100644
index 000000000000..658c9d0dd3b1
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/caching/xml/XmlService.java
@@ -0,0 +1,145 @@
+package org.apache.maven.caching.xml;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.caching.jaxb.BuildDiffType;
+import org.apache.maven.caching.jaxb.BuildInfoType;
+import org.apache.maven.caching.jaxb.CacheReportType;
+import org.apache.maven.caching.jaxb.ObjectFactory;
+import org.codehaus.plexus.component.annotations.Component;
+import org.xml.sax.SAXException;
+
+import javax.xml.XMLConstants;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBElement;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.transform.Source;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.InputStream;
+
+/**
+ * XmlService
+ */
+@Component( role = XmlService.class )
+public class XmlService
+{
+
+ private final ObjectFactory objectFactory;
+ private final JAXBContext jaxbContext;
+ private final Schema schema;
+
+ public XmlService() throws JAXBException, SAXException
+ {
+ objectFactory = new ObjectFactory();
+ jaxbContext = JAXBContext.newInstance( "org.apache.maven.caching.jaxb", XmlService.class.getClassLoader() );
+
+ SchemaFactory sf = SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI );
+ final InputStream domainSchemaStream = getResourceAsStream( "cache-domain.xsd" );
+ final Source domainSchema = new StreamSource( domainSchemaStream );
+ final InputStream configSchemaStream = getResourceAsStream( "cache-config.xsd" );
+ final Source configSchema = new StreamSource( configSchemaStream );
+ schema = sf.newSchema( new Source[] {domainSchema, configSchema} );
+ }
+
+ public byte[] toBytes( BuildInfoType buildInfo )
+ {
+ return serializeXml( objectFactory.createBuild( buildInfo ) );
+ }
+
+ public byte[] toBytes( BuildDiffType diff )
+ {
+ return serializeXml( objectFactory.createDiff( diff ) );
+ }
+
+ public byte[] toBytes( CacheReportType cacheReportType )
+ {
+
+ return serializeXml( objectFactory.createReport( cacheReportType ) );
+ }
+
+ private byte[] serializeXml( JAXBElement> element )
+ {
+ try
+ {
+ Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
+ jaxbMarshaller.setSchema( schema );
+ // output pretty printed
+ jaxbMarshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, true );
+ jaxbMarshaller.setProperty( Marshaller.JAXB_ENCODING, "UTF-8" );
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ jaxbMarshaller.marshal( element, baos );
+ return baos.toByteArray();
+ }
+ catch ( Exception e )
+ {
+ throw new RuntimeException( "Errors in jaxb serialization: " + e.toString(), e );
+ }
+ }
+
+ public T fromFile( Class clazz, File file )
+ {
+
+ try
+ {
+ Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
+ unmarshaller.setSchema( schema );
+ JAXBElement result = (JAXBElement) unmarshaller.unmarshal( file );
+ return result.getValue();
+ }
+ catch ( Exception e )
+ {
+ throw new RuntimeException( "Errors in jaxb serialization: " + e.toString(), e );
+ }
+ }
+
+ public static InputStream getResourceAsStream( String name )
+ {
+ ClassLoader cl = XmlService.class.getClassLoader();
+ if ( cl == null )
+ {
+ // A system class.
+ return ClassLoader.getSystemResourceAsStream( name );
+ }
+ return cl.getResourceAsStream( name );
+ }
+
+ public T fromBytes( Class clazz, byte[] bytes )
+ {
+ try
+ {
+ Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
+ unmarshaller.setSchema( schema );
+ JAXBElement result = (JAXBElement) unmarshaller.unmarshal( new ByteArrayInputStream( bytes ) );
+ return result.getValue();
+
+ }
+ catch ( Exception e )
+ {
+ throw new RuntimeException( "Errors in jaxb serialization: " + e.toString(), e );
+ }
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/graph/DefaultGraphBuilder.java b/maven-core/src/main/java/org/apache/maven/graph/DefaultGraphBuilder.java
index fb7a9f45064d..5a7db543f80b 100644
--- a/maven-core/src/main/java/org/apache/maven/graph/DefaultGraphBuilder.java
+++ b/maven-core/src/main/java/org/apache/maven/graph/DefaultGraphBuilder.java
@@ -34,9 +34,11 @@
import org.apache.maven.MavenExecutionException;
import org.apache.maven.ProjectCycleException;
import org.apache.maven.artifact.ArtifactUtils;
+import org.apache.maven.caching.checksum.KeyUtils;
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.execution.ProjectDependencyGraph;
+import org.apache.maven.model.Dependency;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.building.DefaultModelProblem;
import org.apache.maven.model.building.ModelProblem;
@@ -56,6 +58,8 @@
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.dag.CycleDetectedException;
+import static org.apache.maven.artifact.versioning.VersionRange.createFromVersion;
+
/**
* Builds the {@link ProjectDependencyGraph inter-dependencies graph} between projects in the reactor.
*/
@@ -64,6 +68,8 @@ public class DefaultGraphBuilder
implements GraphBuilder
{
+ public static final String PROJECT_VERSION_PROP_NAME = "org.maven.project.version";
+
@Requirement
private Logger logger;
@@ -81,6 +87,11 @@ public Result build( MavenSession session )
{
final List projects = getProjectsForMavenReactor( session );
validateProjects( projects );
+
+ if ( System.getProperties().containsKey( PROJECT_VERSION_PROP_NAME ) )
+ {
+ overrideReactorVersions( projects, session );
+ }
result = reactorDependencyGraph( session, projects );
}
@@ -100,6 +111,68 @@ public Result build( MavenSession session )
}
}
+ private void overrideReactorVersions( List projects, MavenSession session )
+ {
+ String injectedVersion = System.getProperty( PROJECT_VERSION_PROP_NAME );
+ logger.info( "Overriding reactor projects version to " + injectedVersion );
+
+ Map reactorProjects = new HashMap<>( projects.size() );
+
+ for ( MavenProject project : projects )
+ {
+ String projectKey = KeyUtils.getVersionlessProjectKey( project );
+ logger.debug(
+ "[" + projectKey + "] Overriding version from " + project.getVersion() + " to " + injectedVersion );
+ reactorProjects.put( projectKey, project.getVersion() );
+ project.setVersion( injectedVersion );
+ project.getArtifact().setVersionRange( createFromVersion( injectedVersion ) );
+ project.getArtifact().updateVersion( injectedVersion, session.getLocalRepository() );
+ }
+
+ for ( MavenProject project : projects )
+ {
+ String projectKey = KeyUtils.getVersionlessProjectKey( project );
+ MavenProject parent = project.getParent();
+ String parentKey = KeyUtils.getVersionlessProjectKey( parent );
+ String overriddenParentVersion = reactorProjects.get( parentKey );
+ if ( overriddenParentVersion != null && overriddenParentVersion.equals(
+ project.getParentArtifact().getVersion() ) )
+ {
+ logger.debug(
+ "[" + projectKey + "] Parent " + parentKey + " overriding artefact version from "
+ + parent.getVersion() + " to " + injectedVersion );
+ project.getParentArtifact().setVersionRange( createFromVersion( injectedVersion ) );
+ project.getParentArtifact().updateVersion( injectedVersion, session.getLocalRepository() );
+ }
+
+ for ( Dependency dependency : project.getDependencies() )
+ {
+ String dependencyKey = KeyUtils.getVersionlessDependencyKey( dependency );
+ String overriddenReactorVersion = reactorProjects.get( dependencyKey );
+ if ( overriddenReactorVersion != null && overriddenReactorVersion.equals( dependency.getVersion() ) )
+ {
+ logger.debug(
+ "[" + projectKey + "] Dependency " + dependencyKey + " overriding version from "
+ + dependency.getVersion() + " to " + injectedVersion );
+ dependency.setVersion( injectedVersion );
+ }
+ }
+
+ for ( Dependency dependency : project.getDependencyManagement().getDependencies() )
+ {
+ String dependencyKey = KeyUtils.getVersionlessDependencyKey( dependency );
+ String overriddenReactorVersion = reactorProjects.get( dependencyKey );
+ if ( overriddenReactorVersion != null && overriddenReactorVersion.equals( dependency.getVersion() ) )
+ {
+ logger.debug(
+ "[" + projectKey + "] Dependency management " + dependencyKey
+ + " overriding version from " + dependency.getVersion() + " to " + injectedVersion );
+ dependency.setVersion( injectedVersion );
+ }
+ }
+ }
+ }
+
private Result sessionDependencyGraph( final MavenSession session )
throws CycleDetectedException, DuplicateProjectException
{
diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DependencyContext.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DependencyContext.java
index 7ee499f4b1a0..41004bdb118c 100644
--- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DependencyContext.java
+++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/DependencyContext.java
@@ -31,13 +31,13 @@
* Context of dependency artifacts for a particular project.
*
* NOTE: This class is not part of any public api and can be changed or deleted without prior notice.
- *
+ *
* @since 3.0
* @author Benjamin Bentmann
* @author Kristian Rosenvold (class extract only)
*/
// TODO From a concurrency perspective, this class is not good. The combination of mutable/immutable state is not nice
-public class DependencyContext
+public class DependencyContext implements IDependencyContext
{
private static final Collection> UNRESOLVED = Arrays.asList();
@@ -66,46 +66,53 @@ public DependencyContext( MavenProject project, Collection scopesToColle
scopesToResolveForAggregatedProjects = Collections.synchronizedSet( new TreeSet() );
}
+ @Override
public MavenProject getProject()
{
return project;
}
+ @Override
public Collection getScopesToCollectForCurrentProject()
{
return scopesToCollectForCurrentProject;
}
+ @Override
public Collection getScopesToResolveForCurrentProject()
{
return scopesToResolveForCurrentProject;
}
+ @Override
public Collection getScopesToCollectForAggregatedProjects()
{
return scopesToCollectForAggregatedProjects;
}
+ @Override
public Collection getScopesToResolveForAggregatedProjects()
{
return scopesToResolveForAggregatedProjects;
}
+ @Override
public boolean isResolutionRequiredForCurrentProject()
{
- return lastDependencyArtifacts != project.getDependencyArtifacts() || ( lastDependencyArtifacts != null
- && lastDependencyArtifactCount != lastDependencyArtifacts.size() );
+ return lastDependencyArtifacts != project.getDependencyArtifacts()
+ || ( lastDependencyArtifacts != null && lastDependencyArtifactCount != lastDependencyArtifacts.size() );
}
+ @Override
public boolean isResolutionRequiredForAggregatedProjects( Collection scopesToCollect,
Collection scopesToResolve )
{
- boolean required =
- scopesToCollectForAggregatedProjects.addAll( scopesToCollect )
- || scopesToResolveForAggregatedProjects.addAll( scopesToResolve );
+ boolean required = scopesToCollectForAggregatedProjects.addAll(
+ scopesToCollect ) || scopesToResolveForAggregatedProjects.addAll( scopesToResolve );
return required;
}
+ @Override
public void synchronizeWithProjectState()
{
lastDependencyArtifacts = project.getDependencyArtifacts();
diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/IDependencyContext.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/IDependencyContext.java
new file mode 100644
index 000000000000..1f81561b1c7f
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/IDependencyContext.java
@@ -0,0 +1,47 @@
+package org.apache.maven.lifecycle.internal;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.project.MavenProject;
+
+import java.util.Collection;
+
+/**
+ * IDependencyContext
+ */
+public interface IDependencyContext
+{
+ MavenProject getProject();
+
+ Collection getScopesToCollectForCurrentProject();
+
+ Collection getScopesToResolveForCurrentProject();
+
+ Collection getScopesToCollectForAggregatedProjects();
+
+ Collection getScopesToResolveForAggregatedProjects();
+
+ boolean isResolutionRequiredForCurrentProject();
+
+ boolean isResolutionRequiredForAggregatedProjects( Collection scopesToCollect,
+ Collection scopesToResolve );
+
+ void synchronizeWithProjectState();
+}
diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/MojoExecutor.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/MojoExecutor.java
index b78f54dc42f3..59f384983d9e 100644
--- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/MojoExecutor.java
+++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/MojoExecutor.java
@@ -22,13 +22,23 @@
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
import org.apache.maven.artifact.resolver.filter.CumulativeScopeArtifactFilter;
+import org.apache.maven.caching.CacheController;
+import org.apache.maven.caching.CacheResult;
+import org.apache.maven.caching.MojoExecutionManager;
+import org.apache.maven.caching.MojoParametersListener;
+import org.apache.maven.caching.xml.BuildInfo;
+import org.apache.maven.caching.xml.CacheConfig;
+import org.apache.maven.caching.xml.CacheState;
import org.apache.maven.execution.ExecutionEvent;
import org.apache.maven.execution.MavenSession;
+import org.apache.maven.execution.MojoExecutionEvent;
+import org.apache.maven.execution.MojoExecutionListener;
import org.apache.maven.lifecycle.LifecycleExecutionException;
import org.apache.maven.lifecycle.MissingProjectException;
import org.apache.maven.plugin.BuildPluginManager;
import org.apache.maven.plugin.MavenPluginManager;
import org.apache.maven.plugin.MojoExecution;
+import org.apache.maven.plugin.MojoExecution.Source;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.PluginConfigurationException;
@@ -38,6 +48,7 @@
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
+import org.codehaus.plexus.logging.Logger;
import org.codehaus.plexus.util.StringUtils;
import java.util.ArrayList;
@@ -48,6 +59,13 @@
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.apache.maven.caching.ProjectUtils.isLaterPhase;
+import static org.apache.maven.caching.checksum.KeyUtils.getVersionlessProjectKey;
+import static org.apache.maven.caching.xml.CacheState.DISABLED;
+import static org.apache.maven.caching.xml.CacheState.INITIALIZED;
+
/**
*
@@ -64,6 +82,9 @@
public class MojoExecutor
{
+ @Requirement
+ private Logger logger;
+
@Requirement
private BuildPluginManager pluginManager;
@@ -76,6 +97,15 @@ public class MojoExecutor
@Requirement
private ExecutionEventCatapult eventCatapult;
+ @Requirement
+ private CacheController cacheController;
+
+ @Requirement
+ private CacheConfig cacheConfig;
+
+ @Requirement( role = MojoExecutionListener.class, hint = "MojoParametersListener" )
+ private MojoParametersListener mojoListener;
+
public MojoExecutor()
{
}
@@ -120,7 +150,7 @@ else if ( Artifact.SCOPE_RUNTIME.equals( classpath ) )
else if ( Artifact.SCOPE_COMPILE_PLUS_RUNTIME.equals( classpath ) )
{
scopes = Arrays.asList( Artifact.SCOPE_COMPILE, Artifact.SCOPE_SYSTEM, Artifact.SCOPE_PROVIDED,
- Artifact.SCOPE_RUNTIME );
+ Artifact.SCOPE_RUNTIME );
}
else if ( Artifact.SCOPE_RUNTIME_PLUS_SYSTEM.equals( classpath ) )
{
@@ -129,37 +159,215 @@ else if ( Artifact.SCOPE_RUNTIME_PLUS_SYSTEM.equals( classpath ) )
else if ( Artifact.SCOPE_TEST.equals( classpath ) )
{
scopes = Arrays.asList( Artifact.SCOPE_COMPILE, Artifact.SCOPE_SYSTEM, Artifact.SCOPE_PROVIDED,
- Artifact.SCOPE_RUNTIME, Artifact.SCOPE_TEST );
+ Artifact.SCOPE_RUNTIME, Artifact.SCOPE_TEST );
}
}
return Collections.unmodifiableCollection( scopes );
}
public void execute( MavenSession session, List mojoExecutions, ProjectIndex projectIndex )
- throws LifecycleExecutionException
-
+ throws LifecycleExecutionException
{
DependencyContext dependencyContext = newDependencyContext( session, mojoExecutions );
PhaseRecorder phaseRecorder = new PhaseRecorder( session.getCurrentProject() );
+ final MavenProject project = session.getCurrentProject();
+ final Source source = getSource( mojoExecutions );
+
+ // execute clean bound goals before restoring to not interfere/slowdown clean
+ CacheState cacheState = DISABLED;
+ CacheResult result = CacheResult.empty();
+ if ( source == Source.LIFECYCLE )
+ {
+ List cleanPhase = getCleanPhase( mojoExecutions );
+ for ( MojoExecution mojoExecution : cleanPhase )
+ {
+ execute( session, mojoExecution, projectIndex, dependencyContext, phaseRecorder );
+ }
+ cacheState = cacheConfig.initialize( project, session );
+ if ( cacheState == INITIALIZED )
+ {
+ result = cacheController.findCachedBuild( session, project, projectIndex, mojoExecutions );
+ }
+ }
+
+ boolean restorable = result.isSuccess() || result.isPartialSuccess();
+ boolean restored = result.isSuccess(); // if partially restored need to save increment
+ if ( restorable )
+ {
+ restored &= restoreProject( result, mojoExecutions, projectIndex, dependencyContext, phaseRecorder );
+ }
+ else
+ {
+ for ( MojoExecution mojoExecution : mojoExecutions )
+ {
+ if ( source == Source.CLI || isLaterPhase( mojoExecution.getLifecyclePhase(), "post-clean" ) )
+ {
+ execute( session, mojoExecution, projectIndex, dependencyContext, phaseRecorder );
+ }
+ }
+ }
+
+ if ( cacheState == INITIALIZED && ( !restorable || !restored ) )
+ {
+ final Map executionEvents = mojoListener.getProjectExecutions( project );
+ cacheController.save( result, mojoExecutions, executionEvents );
+ }
+
+ if ( cacheConfig.isFailFast() && !result.isSuccess() )
+ {
+ throw new LifecycleExecutionException(
+ "Failed to restore project[" + getVersionlessProjectKey( project ) + "] from cache, failing build.",
+ project );
+ }
+ }
+
+ private Source getSource( List mojoExecutions )
+ {
+ if ( mojoExecutions == null || mojoExecutions.isEmpty() )
+ {
+ return null;
+ }
for ( MojoExecution mojoExecution : mojoExecutions )
+ {
+ if ( mojoExecution.getSource() == Source.CLI )
+ {
+ return Source.CLI;
+ }
+ }
+ return Source.LIFECYCLE;
+ }
+
+ private boolean restoreProject( CacheResult cacheResult,
+ List mojoExecutions,
+ ProjectIndex projectIndex,
+ DependencyContext dependencyContext,
+ PhaseRecorder phaseRecorder ) throws LifecycleExecutionException
+ {
+
+ final BuildInfo buildInfo = cacheResult.getBuildInfo();
+ final MavenProject project = cacheResult.getContext().getProject();
+ final MavenSession session = cacheResult.getContext().getSession();
+ final List cachedSegment = buildInfo.getCachedSegment( mojoExecutions );
+
+ boolean restored = cacheController.restoreProjectArtifacts( cacheResult );
+ if ( !restored )
+ {
+ logger.info(
+ "[CACHE][" + project.getArtifactId()
+ + "] Cannot restore project artifacts, continuing with non cached build" );
+ return false;
+ }
+
+ for ( MojoExecution cacheCandidate : cachedSegment )
+ {
+
+ if ( cacheController.isForcedExecution( project, cacheCandidate ) )
+ {
+ logger.info(
+ "[CACHE][" + project.getArtifactId() + "] Mojo execution is forced by project property: "
+ + cacheCandidate.getMojoDescriptor().getFullGoalName() );
+ execute( session, cacheCandidate, projectIndex, dependencyContext, phaseRecorder );
+ }
+ else
+ {
+ restored = verifyCacheConsistency( cacheCandidate, buildInfo, project, session, projectIndex,
+ dependencyContext, phaseRecorder );
+ if ( !restored )
+ {
+ break;
+ }
+ }
+ }
+
+ if ( !restored )
+ {
+ // cleanup partial state
+ project.getArtifact().setFile( null );
+ project.getArtifact().setResolved( false );
+ project.getAttachedArtifacts().clear();
+ mojoListener.remove( project );
+ // build as usual
+ for ( MojoExecution mojoExecution : cachedSegment )
+ {
+ execute( session, mojoExecution, projectIndex, dependencyContext, phaseRecorder );
+ }
+ }
+
+ for ( MojoExecution mojoExecution : buildInfo.getPostCachedSegment( mojoExecutions ) )
{
execute( session, mojoExecution, projectIndex, dependencyContext, phaseRecorder );
}
+ return restored;
+ }
+
+ private boolean verifyCacheConsistency( MojoExecution cacheCandidate,
+ BuildInfo cachedBuildInfo,
+ MavenProject project,
+ MavenSession session,
+ ProjectIndex projectIndex,
+ DependencyContext dependencyContext,
+ PhaseRecorder phaseRecorder ) throws LifecycleExecutionException
+ {
+
+ AtomicBoolean consistent = new AtomicBoolean( true );
+ final MojoExecutionManager mojoChecker = new MojoExecutionManager( project, cacheController, cachedBuildInfo,
+ consistent, logger, cacheConfig );
+
+ if ( mojoChecker.needCheck( cacheCandidate, session ) )
+ {
+ try
+ {
+ // actual execution will not happen (if not forced). decision delayed to execution time
+ // then all properties are resolved.
+ cacheCandidate.setMojoExecutionManager( mojoChecker );
+ IDependencyContext nop = new NoResolutionContext( dependencyContext );
+ execute( session, cacheCandidate, projectIndex, nop, phaseRecorder );
+ }
+ finally
+ {
+ cacheCandidate.setMojoExecutionManager( null );
+ }
+ }
+ else
+ {
+ logger.info(
+ "[CACHE][" + project.getArtifactId() + "] Skipping plugin execution (cached): "
+ + cacheCandidate.getMojoDescriptor().getFullGoalName() );
+ }
+
+ return consistent.get();
+ }
+
+ private List getCleanPhase( List mojoExecutions )
+ {
+ List list = new ArrayList<>();
+ for ( MojoExecution mojoExecution : mojoExecutions )
+ {
+ if ( isLaterPhase( mojoExecution.getLifecyclePhase(), "post-clean" ) )
+ {
+ break;
+ }
+ list.add( mojoExecution );
+ }
+ return list;
}
- public void execute( MavenSession session, MojoExecution mojoExecution, ProjectIndex projectIndex,
- DependencyContext dependencyContext, PhaseRecorder phaseRecorder )
- throws LifecycleExecutionException
+ public void execute( MavenSession session,
+ MojoExecution mojoExecution,
+ ProjectIndex projectIndex,
+ IDependencyContext dependencyContext,
+ PhaseRecorder phaseRecorder ) throws LifecycleExecutionException
{
execute( session, mojoExecution, projectIndex, dependencyContext );
phaseRecorder.observeExecution( mojoExecution );
}
- private void execute( MavenSession session, MojoExecution mojoExecution, ProjectIndex projectIndex,
- DependencyContext dependencyContext )
- throws LifecycleExecutionException
+ private void execute( MavenSession session,
+ MojoExecution mojoExecution,
+ ProjectIndex projectIndex,
+ IDependencyContext dependencyContext ) throws LifecycleExecutionException
{
MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor();
@@ -175,18 +383,18 @@ private void execute( MavenSession session, MojoExecution mojoExecution, Project
if ( mojoDescriptor.isProjectRequired() && !session.getRequest().isProjectPresent() )
{
Throwable cause = new MissingProjectException(
- "Goal requires a project to execute" + " but there is no POM in this directory ("
- + session.getExecutionRootDirectory() + ")."
- + " Please verify you invoked Maven from the correct directory." );
+ "Goal requires a project to execute" + " but there is no POM in this directory ("
+ + session.getExecutionRootDirectory() + ")."
+ + " Please verify you invoked Maven from the correct directory." );
throw new LifecycleExecutionException( mojoExecution, null, cause );
}
if ( mojoDescriptor.isOnlineRequired() && session.isOffline() )
{
- if ( MojoExecution.Source.CLI.equals( mojoExecution.getSource() ) )
+ if ( Source.CLI.equals( mojoExecution.getSource() ) )
{
Throwable cause = new IllegalStateException(
- "Goal requires online mode for execution" + " but Maven is currently offline." );
+ "Goal requires online mode for execution" + " but Maven is currently offline." );
throw new LifecycleExecutionException( mojoExecution, session.getCurrentProject(), cause );
}
else
@@ -209,8 +417,10 @@ private void execute( MavenSession session, MojoExecution mojoExecution, Project
{
pluginManager.executeMojo( session, mojoExecution );
}
- catch ( MojoFailureException | PluginManagerException | PluginConfigurationException
- | MojoExecutionException e )
+ catch ( MojoFailureException
+ | PluginManagerException
+ | PluginConfigurationException
+ | MojoExecutionException e )
{
throw new LifecycleExecutionException( mojoExecution, session.getCurrentProject(), e );
}
@@ -232,9 +442,9 @@ private void execute( MavenSession session, MojoExecution mojoExecution, Project
}
}
- public void ensureDependenciesAreResolved( MojoDescriptor mojoDescriptor, MavenSession session,
- DependencyContext dependencyContext )
- throws LifecycleExecutionException
+ public void ensureDependenciesAreResolved( MojoDescriptor mojoDescriptor,
+ MavenSession session,
+ IDependencyContext dependencyContext ) throws LifecycleExecutionException
{
MavenProject project = dependencyContext.getProject();
@@ -246,7 +456,7 @@ public void ensureDependenciesAreResolved( MojoDescriptor mojoDescriptor, MavenS
Collection scopesToResolve = dependencyContext.getScopesToResolveForCurrentProject();
lifeCycleDependencyResolver.resolveProjectDependencies( project, scopesToCollect, scopesToResolve, session,
- aggregating, Collections.emptySet() );
+ aggregating, Collections.emptySet() );
dependencyContext.synchronizeWithProjectState();
}
@@ -263,17 +473,15 @@ public void ensureDependenciesAreResolved( MojoDescriptor mojoDescriptor, MavenS
if ( aggregatedProject != project )
{
lifeCycleDependencyResolver.resolveProjectDependencies( aggregatedProject, scopesToCollect,
- scopesToResolve, session, aggregating,
- Collections.emptySet() );
+ scopesToResolve, session, aggregating, Collections.emptySet() );
}
}
}
}
ArtifactFilter artifactFilter = getArtifactFilter( mojoDescriptor );
- List projectsToResolve =
- LifecycleDependencyResolver.getProjects( session.getCurrentProject(), session,
- mojoDescriptor.isAggregator() );
+ List projectsToResolve = LifecycleDependencyResolver.getProjects( session.getCurrentProject(),
+ session, mojoDescriptor.isAggregator() );
for ( MavenProject projectToResolve : projectsToResolve )
{
projectToResolve.setArtifactFilter( artifactFilter );
@@ -305,9 +513,9 @@ private ArtifactFilter getArtifactFilter( MojoDescriptor mojoDescriptor )
}
}
- public List executeForkedExecutions( MojoExecution mojoExecution, MavenSession session,
- ProjectIndex projectIndex )
- throws LifecycleExecutionException
+ public List executeForkedExecutions( MojoExecution mojoExecution,
+ MavenSession session,
+ ProjectIndex projectIndex ) throws LifecycleExecutionException
{
List forkedProjects = Collections.emptyList();
diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/NoResolutionContext.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/NoResolutionContext.java
new file mode 100644
index 000000000000..e5ba54545ab0
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/NoResolutionContext.java
@@ -0,0 +1,88 @@
+package org.apache.maven.lifecycle.internal;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+import org.apache.maven.project.MavenProject;
+
+import java.util.Collection;
+
+/**
+ * NoResolutionContext
+ */
+public class NoResolutionContext implements IDependencyContext
+{
+ private final DependencyContext delegate;
+
+ public NoResolutionContext( DependencyContext delegate )
+ {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public MavenProject getProject()
+ {
+ return delegate.getProject();
+ }
+
+ @Override
+ public Collection getScopesToCollectForCurrentProject()
+ {
+ return delegate.getScopesToCollectForCurrentProject();
+ }
+
+ @Override
+ public Collection getScopesToResolveForCurrentProject()
+ {
+
+ return delegate.getScopesToResolveForCurrentProject();
+ }
+
+ @Override
+ public Collection getScopesToCollectForAggregatedProjects()
+ {
+ return delegate.getScopesToCollectForAggregatedProjects();
+ }
+
+ @Override
+ public Collection getScopesToResolveForAggregatedProjects()
+ {
+ return delegate.getScopesToCollectForAggregatedProjects();
+ }
+
+ @Override
+ public boolean isResolutionRequiredForCurrentProject()
+ {
+ return false;
+ }
+
+ @Override
+ public boolean isResolutionRequiredForAggregatedProjects( Collection scopesToCollect,
+ Collection scopesToResolve )
+ {
+ return false;
+ }
+
+ @Override
+ public void synchronizeWithProjectState()
+ {
+ delegate.synchronizeWithProjectState();
+ }
+}
diff --git a/maven-core/src/main/java/org/apache/maven/plugin/DefaultBuildPluginManager.java b/maven-core/src/main/java/org/apache/maven/plugin/DefaultBuildPluginManager.java
index 1a007424e760..e3b35ccf7182 100644
--- a/maven-core/src/main/java/org/apache/maven/plugin/DefaultBuildPluginManager.java
+++ b/maven-core/src/main/java/org/apache/maven/plugin/DefaultBuildPluginManager.java
@@ -44,8 +44,7 @@
* DefaultBuildPluginManager
*/
@Component( role = BuildPluginManager.class )
-public class DefaultBuildPluginManager
- implements BuildPluginManager
+public class DefaultBuildPluginManager implements BuildPluginManager
{
@Requirement
@@ -74,8 +73,8 @@ public void setMojoExecutionListeners( final List listene
* @param repositories
* @param session
* @return PluginDescriptor The component descriptor for the Maven plugin.
- * @throws PluginNotFoundException The plugin could not be found in any repositories.
- * @throws PluginResolutionException The plugin could be found but could not be resolved.
+ * @throws PluginNotFoundException The plugin could not be found in any repositories.
+ * @throws PluginResolutionException The plugin could be found but could not be resolved.
* @throws InvalidPluginDescriptorException
*/
public PluginDescriptor loadPlugin( Plugin plugin, List repositories,
@@ -134,7 +133,10 @@ public void executeMojo( MavenSession session, MojoExecution mojoExecution )
mojoExecutionListener.beforeMojoExecution( mojoExecutionEvent );
- mojo.execute();
+ if ( mojoExecution.canRun( mojo, session ) )
+ {
+ mojo.execute();
+ }
mojoExecutionListener.afterMojoExecutionSuccess( mojoExecutionEvent );
}
@@ -150,20 +152,20 @@ public void executeMojo( MavenSession session, MojoExecution mojoExecution )
}
catch ( PluginContainerException e )
{
- mojoExecutionListener.afterExecutionFailure( new MojoExecutionEvent( session, project, mojoExecution, mojo,
- e ) );
+ mojoExecutionListener.afterExecutionFailure(
+ new MojoExecutionEvent( session, project, mojoExecution, mojo, e ) );
throw new PluginExecutionException( mojoExecution, project, e );
}
catch ( NoClassDefFoundError e )
{
- mojoExecutionListener.afterExecutionFailure( new MojoExecutionEvent( session, project, mojoExecution, mojo,
- e ) );
+ mojoExecutionListener.afterExecutionFailure(
+ new MojoExecutionEvent( session, project, mojoExecution, mojo, e ) );
ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 );
PrintStream ps = new PrintStream( os );
- ps.println( "A required class was missing while executing " + mojoDescriptor.getId() + ": "
- + e.getMessage() );
+ ps.println(
+ "A required class was missing while executing " + mojoDescriptor.getId() + ": " + e.getMessage() );
pluginRealm.display( ps );
Exception wrapper = new PluginContainerException( mojoDescriptor, pluginRealm, os.toString(), e );
@@ -172,8 +174,8 @@ public void executeMojo( MavenSession session, MojoExecution mojoExecution )
}
catch ( LinkageError e )
{
- mojoExecutionListener.afterExecutionFailure( new MojoExecutionEvent( session, project, mojoExecution, mojo,
- e ) );
+ mojoExecutionListener.afterExecutionFailure(
+ new MojoExecutionEvent( session, project, mojoExecution, mojo, e ) );
ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 );
PrintStream ps = new PrintStream( os );
@@ -187,8 +189,8 @@ public void executeMojo( MavenSession session, MojoExecution mojoExecution )
}
catch ( ClassCastException e )
{
- mojoExecutionListener.afterExecutionFailure( new MojoExecutionEvent( session, project, mojoExecution, mojo,
- e ) );
+ mojoExecutionListener.afterExecutionFailure(
+ new MojoExecutionEvent( session, project, mojoExecution, mojo, e ) );
ByteArrayOutputStream os = new ByteArrayOutputStream( 1024 );
PrintStream ps = new PrintStream( os );
@@ -200,8 +202,8 @@ public void executeMojo( MavenSession session, MojoExecution mojoExecution )
}
catch ( RuntimeException e )
{
- mojoExecutionListener.afterExecutionFailure( new MojoExecutionEvent( session, project, mojoExecution, mojo,
- e ) );
+ mojoExecutionListener.afterExecutionFailure(
+ new MojoExecutionEvent( session, project, mojoExecution, mojo, e ) );
throw e;
}
@@ -218,8 +220,8 @@ public void executeMojo( MavenSession session, MojoExecution mojoExecution )
}
/**
- * TODO pluginDescriptor classRealm and artifacts are set as a side effect of this
- * call, which is not nice.
+ * TODO pluginDescriptor classRealm and artifacts are set as a side effect of this call, which is not nice.
+ *
* @throws PluginResolutionException
*/
public ClassRealm getPluginRealm( MavenSession session, PluginDescriptor pluginDescriptor )
diff --git a/maven-core/src/main/java/org/apache/maven/plugin/MojoCheker.java b/maven-core/src/main/java/org/apache/maven/plugin/MojoCheker.java
new file mode 100644
index 000000000000..c82064b78bbf
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/plugin/MojoCheker.java
@@ -0,0 +1,38 @@
+package org.apache.maven.plugin;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.execution.MavenSession;
+
+/**
+ * MojoCheker
+ */
+public interface MojoCheker
+{
+ /**
+ * @return true if this execution needs to be checked
+ */
+ boolean needCheck( MojoExecution mojoExecution, MavenSession session );
+
+ /**
+ * @return true if check passed and mojo could be run
+ */
+ boolean check( MojoExecution mojoExecution, Mojo mojo, MavenSession session );
+}
diff --git a/maven-core/src/main/java/org/apache/maven/plugin/MojoExecution.java b/maven-core/src/main/java/org/apache/maven/plugin/MojoExecution.java
index fa72c1866e4c..0cac2a7fba9e 100644
--- a/maven-core/src/main/java/org/apache/maven/plugin/MojoExecution.java
+++ b/maven-core/src/main/java/org/apache/maven/plugin/MojoExecution.java
@@ -19,14 +19,15 @@
* under the License.
*/
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
+import org.apache.maven.execution.MavenSession;
import org.apache.maven.model.Plugin;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.codehaus.plexus.util.xml.Xpp3Dom;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
/**
* MojoExecution
*/
@@ -41,8 +42,15 @@ public class MojoExecution
private MojoDescriptor mojoDescriptor;
+ private MojoCheker mojoCheker;
+
private Xpp3Dom configuration;
+ public boolean canRun( Mojo mojo, MavenSession session )
+ {
+ return mojoCheker == null || mojoCheker.check( this, mojo, session );
+ }
+
/**
* Describes the source of an execution.
*/
@@ -63,8 +71,8 @@ public enum Source
private Source source = Source.LIFECYCLE;
/**
- * The phase may or may not have been bound to a phase but once the plan has been calculated we know what phase
- * this mojo execution is going to run in.
+ * The phase may or may not have been bound to a phase but once the plan has been calculated we know what phase this
+ * mojo execution is going to run in.
*/
private String lifecyclePhase;
@@ -237,4 +245,8 @@ public void setForkedExecutions( String projectKey, List forkedEx
this.forkedExecutions.put( projectKey, forkedExecutions );
}
+ public void setMojoExecutionManager( MojoCheker mojoCheker )
+ {
+ this.mojoCheker = mojoCheker;
+ }
}
diff --git a/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java b/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
index 7e18f1ef3ce2..b611758bf565 100644
--- a/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
+++ b/maven-core/src/main/java/org/apache/maven/project/DefaultProjectBuilder.java
@@ -19,20 +19,6 @@
* under the License.
*/
-import java.io.File;
-import java.io.IOException;
-import java.util.AbstractMap;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
import org.apache.maven.RepositoryUtils;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.InvalidArtifactRTException;
@@ -76,6 +62,20 @@
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResult;
+import java.io.File;
+import java.io.IOException;
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
/**
* DefaultProjectBuilder
*/
@@ -675,6 +675,7 @@ private void initProject( MavenProject project, Map projec
project.setModel( model );
project.setOriginalModel( result.getRawModel() );
+ project.setOriginalEffectiveModel( model.clone() );
project.setFile( model.getPomFile() );
initParent( project, projects, buildParentIfNotExisting, result, projectBuildingRequest );
@@ -791,7 +792,7 @@ private void initProject( MavenProject project, Map projec
HashMap delegate;
@Override
- public Set> entrySet()
+ public Set> entrySet()
{
return Collections.unmodifiableSet( compute().entrySet() );
}
diff --git a/maven-core/src/main/java/org/apache/maven/project/MavenProject.java b/maven-core/src/main/java/org/apache/maven/project/MavenProject.java
index ceb39e21a231..b33f441b0c0b 100644
--- a/maven-core/src/main/java/org/apache/maven/project/MavenProject.java
+++ b/maven-core/src/main/java/org/apache/maven/project/MavenProject.java
@@ -151,6 +151,8 @@ public class MavenProject
private Model originalModel;
+ private Model originalEffectiveModel;
+
private Map pluginArtifactMap;
private Set reportArtifacts;
@@ -1032,6 +1034,16 @@ public Model getOriginalModel()
return originalModel;
}
+ public Model getOriginalEffectiveModel()
+ {
+ return originalEffectiveModel;
+ }
+
+ public void setOriginalEffectiveModel( Model originalEffectiveModel )
+ {
+ this.originalEffectiveModel = originalEffectiveModel;
+ }
+
public void setManagedVersionMap( Map map )
{
managedVersionMap = map;
diff --git a/maven-core/src/main/resources/cache-config-instance.xml b/maven-core/src/main/resources/cache-config-instance.xml
new file mode 100644
index 000000000000..79f7422c24c4
--- /dev/null
+++ b/maven-core/src/main/resources/cache-config-instance.xml
@@ -0,0 +1,92 @@
+
+
+
+
+ true
+ SHA-256
+ true
+
+ http://host:port
+
+
+ 3
+
+
+
+
+
+
+ {*.java,*.groovy,*.yaml,*.svcd,*.proto,*assembly.xml,assembly*.xml,*logback.xml,*.vm,*.ini,*.jks,*.properties,*.sh,*.bat}
+
+ src/
+ pom.xml
+
+
+
+ 111
+
+
+
+
+
+
+
+ 1
+ 2
+
+
+
+
+
+
+
+
+
+
+
+ aaa
+
+
+ install
+
+
+ deploy
+
+
+ deploy-local
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/maven-core/src/main/resources/cache-config.xsd b/maven-core/src/main/resources/cache-config.xsd
new file mode 100644
index 000000000000..1f5eb4eb9be6
--- /dev/null
+++ b/maven-core/src/main/resources/cache-config.xsd
@@ -0,0 +1,620 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Cached build metadata
+
+
+
+
+
+
+
+
+
+ Configuration of major cache properties
+
+
+
+
+
+
+ Configuration for source code input files participating in checksum calculation
+
+
+
+
+
+
+ Configuration for output artifacts, it's needed if you want to explicitly include/exclude something from caching
+
+
+
+
+
+
+ Execution rules for plugins in cached mode. Defines which plugins should run always
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 64 bit XX family hashing. Fast, but higher probability of collisions
+
+
+
+
+
+
+ 64 bit XX family hashing, with usage of Memory Mapped Buffer
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Validate cache config and builds metadata against xsd.
+
+
+
+
+
+
+ Specifies how to identify belonging to a cached project then submodule is being build.
+
+
+
+
+
+
+
+ Any project dependency this this version will be considered cache eligible and will
+ be processed cache aware
+
+
+
+
+
+
+
+
+
+
+
+
+ address of remote cache
+
+
+
+
+
+
+
+
+ Save output to remote cache. Recommended to enable on CI agents only.
+
+
+
+
+
+
+
+
+
+
+
+ Directory name in build output directory to attach to cached artifacts
+
+
+
+
+
+
+
+
+
+
+
+
+ Maximum number of cached build per artifact in local cache. First created cache (the
+ oldest) is
+ evicted if breached.
+
+
+
+
+
+
+
+
+
+
+
+
+ Causes file hash is saved in build metadata
+
+
+
+
+
+
+ Causes effective pom info is saved in build metadata
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Global input calculation rules applicable to all projects and plugins in the build
+
+
+
+
+
+
+ Plugin specific input calculation rules
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Effective pom calculation rules
+
+
+
+
+
+
+
+ Plugin configuration property should be excluded from effective pom
+ calculation
+
+
+
+
+
+
+
+
+
+ Specifies plugin level rules of configuration processing in search of referenced source
+ files
+
+
+
+
+
+
+ Specifies execution specific configuration processing in search of referenced source
+ files
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Specifies rules of configuration processing in search of referenced source files
+
+
+
+
+
+
+
+ ignore parent config or inherit/merge
+
+
+
+
+
+
+
+
+ Common attributes for scanning paths
+
+
+
+
+
+ Should walk directory specified in property recursively or not
+
+
+
+
+
+
+ Glob to apply when scanning dir denoted by this property
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Forces cache to treat property value as input and include in calculation. If set, only included
+ properties will be takein in calculation (whitelist)
+
+
+
+
+
+
+
+ Tag to exclude when scanning plugin configuration for input files (blacklist)
+
+
+
+
+
+
+ Additional processing rules for non-blacklisted tags
+
+
+
+
+
+
+
+
+
+ Ignore parent settings or inherit and merge
+
+
+
+
+
+
+
+
+
+
+ Scan directory accordingly to cache implementation
+
+
+
+
+
+
+ Skip directory
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Patterns to exclude output artifacts applicable to all projects in the build
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Specify property which should be reconciled against running build
+
+
+
+
+
+
+ Specify property which should be logged to build metadata for exploration
+
+
+
+
+
+
+ Specify property which should not be logged
+
+
+
+
+
+
+
+ Controls if all plugin properties to be logged (true is default). All the properties logged
+ with respect to log/nolog children:
+ * true: logged all if no blacklists () and whitelists () specified on plugin
+ level
+ * false: logged only tracked and included by whitelists () on plugin level
+
+
+
+
+
+
+
+
+
+
+
+
+ Specify which plugin should run always if present in build regardless of cached status
+
+
+
+
+
+
+ Specify which executions/plugins/goals do not affect generated artifacts and do not affect build correctness.
+ If cached build lacks of ignorable executions only, it still could be reused.
+ Typically case is then cached build is produced with 'verify' and you locally you run 'install'.
+ Strictly speaking these are different builds but in most of cases you want this difference to be ignored
+
+
+
+
+
+
+ Specify which plugin should run always if present in build regardless of cached status
+
+
+
+
+
+
+
+ Reconciliation rules for plugin properties which might be affected by command line
+ flags, etc
+
+
+
+
+
+
+
+ Controls if all plugin properties to be logged (true is default). All the properties
+ logged with respect to children:
+ * logAll on plugin level overrides global value
+ * true: logged all if no blacklists () and whitelists () specified on
+ plugin level
+ * false: logged only tracked and included by whitelists () on plugin level
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Specify which plugin should run always if present in build regardless of cached status
+
+
+
+
+
+
+ Specify which executions should run always if present in build regardless of cached status
+
+
+
+
+
+
+ Specify which goals should run always if present in build regardless of cached status
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Goals identification
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Executions ids list with plugin identifier
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Specify which value denotes skipped execution in plugin config.
+ If active build skips execution (property set to skipValue) cache will allow such
+ discrepancy.
+
+
+
+
+
+
+ Manual value for reconciliation. Required to reconcile runtime only properties
+
+
+
+
+
+
+
+
diff --git a/maven-core/src/main/resources/cache-domain-instance.xml b/maven-core/src/main/resources/cache-domain-instance.xml
new file mode 100644
index 000000000000..1c859de7f831
--- /dev/null
+++ b/maven-core/src/main/resources/cache-domain-instance.xml
@@ -0,0 +1,44 @@
+
+
+ v3
+ 1980-03-23T10:20:15
+ my-server.com
+ SHA-256
+
+ clean
+ verify
+
+
+ com.my.shared
+ my-server
+ jar
+ my-server.jar
+ hashvalue
+ 123456
+
+
+
+ com.my.shared
+ my-server
+ java-source
+ sources
+ my-server-sources.jar
+
+
+
+
+ default:check:verify:duplicate-finder-maven-plugin:org.basepom.maven:1.3.0
+ org.basepom.mojo.duplicatefinder.DuplicateFinderMojo
+
+ true
+ false
+
+
+
+
+ 0136171ac2c9d9b066805bd09904a25318cfb2037189b20c48e79b20b63b81ed
+ src/main/conf/my-context.xml
+
+
diff --git a/maven-core/src/main/resources/cache-domain.xsd b/maven-core/src/main/resources/cache-domain.xsd
new file mode 100644
index 000000000000..48446ca7370c
--- /dev/null
+++ b/maven-core/src/main/resources/cache-domain.xsd
@@ -0,0 +1,167 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/maven-core/src/test/java/org/apache/maven/caching/BuildInfoTest.java b/maven-core/src/test/java/org/apache/maven/caching/BuildInfoTest.java
new file mode 100644
index 000000000000..6c9b14c418aa
--- /dev/null
+++ b/maven-core/src/test/java/org/apache/maven/caching/BuildInfoTest.java
@@ -0,0 +1,113 @@
+package org.apache.maven.caching;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+import com.google.common.collect.Lists;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.DefaultArtifact;
+import org.apache.maven.artifact.handler.DefaultArtifactHandler;
+import org.apache.maven.caching.hash.HashFactory;
+import org.apache.maven.caching.jaxb.ArtifactType;
+import org.apache.maven.caching.jaxb.BuildInfoType;
+import org.apache.maven.caching.jaxb.CompletedExecutionType;
+import org.apache.maven.caching.jaxb.DigestItemType;
+import org.apache.maven.caching.jaxb.ProjectsInputInfoType;
+import org.apache.maven.caching.jaxb.PropertyValueType;
+import org.apache.maven.caching.xml.BuildInfo;
+import org.apache.maven.caching.xml.XmlService;
+
+import javax.xml.datatype.DatatypeFactory;
+import java.io.File;
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.GregorianCalendar;
+
+import static org.apache.maven.caching.xml.BuildInfo.createGoals;
+
+public class BuildInfoTest {
+
+ // @Test
+ public void name() throws Exception {
+
+ XmlService xmlService = new XmlService();
+
+ ProjectsInputInfoType main = new ProjectsInputInfoType();
+ main.setChecksum("dependencyChecksum");
+ main.getItem().add(createItem("pom", "4.0.0", "hash1"));
+ main.getItem().add(createItem("file", Paths.get(".").toString(), "hash2"));
+
+ ArtifactType artifact = new ArtifactType();
+ artifact.setGroupId("g");
+ artifact.setArtifactId("a");
+ artifact.setType("t");
+ artifact.setClassifier("c");
+ artifact.setScope("s");
+ artifact.setFileName("f");
+ artifact.setFileSize(BigInteger.valueOf(123456));
+ artifact.setFileHash("456L");
+
+ BuildInfoType buildInfo = new BuildInfoType();
+ buildInfo.setCacheImplementationVersion("cacheImplementationVersion");
+ buildInfo.setBuildServer("server");
+ buildInfo.setBuildTime(DatatypeFactory.newInstance().newXMLGregorianCalendar(new GregorianCalendar()));
+ buildInfo.setArtifact(artifact);
+ buildInfo.setHashFunction("SHA-256");
+ buildInfo.setGoals(createGoals(Lists.newArrayList("install")));
+ final Artifact attachedArtifact = new DefaultArtifact("ag", "aa", "av", "as", "at", "ac", new DefaultArtifactHandler());
+ buildInfo.setAttachedArtifacts(BuildInfo.createAttachedArtifacts(Lists.newArrayList(attachedArtifact), HashFactory.XX.createAlgorithm()));
+ buildInfo.setProjectsInputInfo(main);
+ buildInfo.setExecutions(createExecutions());
+
+ byte[] bytes = xmlService.toBytes(buildInfo);
+ System.out.println(new String(bytes, StandardCharsets.UTF_8));
+ Path tempFilePath = Files.createTempFile("test", "test");
+ File file = tempFilePath.toFile();
+ file.deleteOnExit();
+ Files.write(tempFilePath, bytes);
+
+ BuildInfoType buildInfo1 = xmlService.fromFile(BuildInfoType.class, file);
+ System.out.println(buildInfo1);
+ }
+
+ private BuildInfoType.Executions createExecutions() {
+ CompletedExecutionType execution = new CompletedExecutionType();
+ execution.setExecutionKey("execkey");
+ execution.setConfiguration(new CompletedExecutionType.Configuration());
+ PropertyValueType property = new PropertyValueType();
+ property.setValue("value");
+ property.setName("key");
+ execution.getConfiguration().getProperty().add(property);
+ BuildInfoType.Executions executions = new BuildInfoType.Executions();
+ executions.getExecution().add(execution);
+ return executions;
+ }
+
+ private DigestItemType createItem(String pom, String s, String hash1) {
+ final DigestItemType d1 = new DigestItemType();
+ d1.setType(pom);
+ d1.setHash(s);
+ d1.setValue(hash1);
+ return d1;
+ }
+}
\ No newline at end of file
diff --git a/maven-core/src/test/java/org/apache/maven/caching/checksum/MavenProjectInputTest.java b/maven-core/src/test/java/org/apache/maven/caching/checksum/MavenProjectInputTest.java
new file mode 100644
index 000000000000..d420134e6746
--- /dev/null
+++ b/maven-core/src/test/java/org/apache/maven/caching/checksum/MavenProjectInputTest.java
@@ -0,0 +1,85 @@
+package org.apache.maven.caching.checksum;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+public class MavenProjectInputTest {
+
+ private static final String GLOB = "{*-pom.xml}";
+
+ @Before
+ public void setUp() throws Exception {
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ }
+
+ @Test
+ public void testAddInputsRelativePath() {
+// MavenProjectInput inputs = new MavenProjectInput(config, new ArrayList(), Paths.get("src\\test\\resources\\org"), GLOB);
+// ArrayList files = new ArrayList<>();
+// inputs.listDirOrFile("../../resources", inputs.dirGlob, files, new HashSet());
+// assertEquals(4, files.size());
+ }
+
+ @Test
+ public void testAddInputsAbsolutePath() {
+// Path baseDirPath = Paths.get("src\\test\\resources\\org");
+// MavenProjectInput inputs = new MavenProjectInput(config, new ArrayList(), baseDirPath, GLOB);
+// ArrayList files = new ArrayList<>();
+// Path candidatePath = baseDirPath.resolve("../../resources").normalize().toAbsolutePath();
+// inputs.listDirOrFile(candidatePath.toString(), inputs.dirGlob, files, new HashSet());
+// assertEquals(4, files.size()); // pom is filtered out by hardcoded if
+ }
+
+ @Test
+ public void testGlobInput() {
+// Path baseDirPath = Paths.get("src\\test\\resources");
+// MavenProjectInput inputs = new MavenProjectInput(config, new ArrayList(), baseDirPath, GLOB);
+// ArrayList files = new ArrayList<>();
+// inputs.tryAddInputs("*.java", files, new HashSet());
+// assertEquals(0, files.size()); // pom is filtered out by hardcoded if
+ }
+
+ @Test
+ public void testGetDirectoryFiles() {
+ List directoryFiles = new ArrayList<>();
+ MavenProjectInput.walkDirectoryFiles(Paths.get("src/test/resources"), directoryFiles, MavenProjectInput.DEFAULT_GLOB);
+ assertEquals(0, directoryFiles.size()); // pom is filtered out by hardcoded if
+ }
+
+ @Test
+ public void testGetDirectoryFiles2() {
+ List directoryFiles = new ArrayList<>();
+ MavenProjectInput.walkDirectoryFiles(Paths.get("src/test/resources"), directoryFiles, GLOB);
+ assertEquals(4, directoryFiles.size()); // pom is filtered out by hardcoded if
+ }
+}
diff --git a/maven-core/src/test/java/org/apache/maven/caching/checksum/SHAHashTest.java b/maven-core/src/test/java/org/apache/maven/caching/checksum/SHAHashTest.java
new file mode 100644
index 000000000000..9cf5bb405f91
--- /dev/null
+++ b/maven-core/src/test/java/org/apache/maven/caching/checksum/SHAHashTest.java
@@ -0,0 +1,72 @@
+package org.apache.maven.caching.checksum;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.caching.hash.HashAlgorithm;
+import org.apache.maven.caching.hash.HashChecksum;
+import org.junit.Test;
+
+import java.nio.charset.StandardCharsets;
+
+import static org.apache.maven.caching.hash.HashFactory.SHA256;
+import static org.junit.Assert.assertEquals;
+
+public class SHAHashTest {
+ private static final byte[] HELLO_ARRAY = "hello".getBytes(StandardCharsets.UTF_8);
+ private static final byte[] WORLD_ARRAY = "world".getBytes(StandardCharsets.UTF_8);
+ private static final String EMPTY_HASH = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
+ private static final String HELLO_HASH = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824";
+ private static final String WORLD_HASH = "486ea46224d1bb4fb680f34f7c9ad96a8f24ec88be73ea8e5a6c65260e9cb8a7";
+ private static final String HELLO_CHECKSUM = "9595c9df90075148eb06860365df33584b75bff782a510c6cd4883a419833d50";
+ private static final String WORLD_CHECKSUM = "63e5c163c81ee9a3ed99d365ff963ecea340cc455deeac7c4b63ac75b9cf4706";
+ private static final String FULL_CHECKSUM = "7305db9b2abccd706c256db3d97e5ff48d677cfe4d3a5904afb7da0e3950e1e2";
+
+ private static final HashAlgorithm ALGORITHM = SHA256.createAlgorithm();
+ private static final HashChecksum CHECKSUM = SHA256.createChecksum(0);
+
+ @Test
+ public void testEmptyArray() {
+ byte[] emptyArray = new byte[0];
+ String hash = ALGORITHM.hash(emptyArray);
+ assertEquals(EMPTY_HASH, hash);
+ }
+
+ @Test
+ public void testSimpleHash() {
+ String helloHash = ALGORITHM.hash(HELLO_ARRAY);
+ assertEquals(HELLO_HASH, helloHash);
+
+ String worldHash = ALGORITHM.hash(WORLD_ARRAY);
+ assertEquals(WORLD_HASH, worldHash);
+ }
+
+ @Test
+ public void testSimpleChecksum() {
+ assertEquals(HELLO_HASH, CHECKSUM.update(HELLO_ARRAY));
+ assertEquals(HELLO_CHECKSUM, CHECKSUM.digest());
+
+ assertEquals(WORLD_HASH, CHECKSUM.update(WORLD_ARRAY));
+ assertEquals(WORLD_CHECKSUM, CHECKSUM.digest());
+
+ assertEquals(HELLO_HASH, CHECKSUM.update(HELLO_ARRAY));
+ assertEquals(WORLD_HASH, CHECKSUM.update(WORLD_ARRAY));
+ assertEquals(FULL_CHECKSUM, CHECKSUM.digest());
+ }
+}
diff --git a/maven-core/src/test/java/org/apache/maven/caching/checksum/XXHashTest.java b/maven-core/src/test/java/org/apache/maven/caching/checksum/XXHashTest.java
new file mode 100644
index 000000000000..3a1a46ecf492
--- /dev/null
+++ b/maven-core/src/test/java/org/apache/maven/caching/checksum/XXHashTest.java
@@ -0,0 +1,132 @@
+package org.apache.maven.caching.checksum;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.caching.hash.HashAlgorithm;
+import org.apache.maven.caching.hash.HashChecksum;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+import static org.apache.maven.caching.hash.HashFactory.XX;
+import static org.apache.maven.caching.hash.HashFactory.XXMM;
+import static org.junit.Assert.assertEquals;
+
+public class XXHashTest {
+ private static final byte[] HELLO_ARRAY = "hello".getBytes(StandardCharsets.UTF_8);
+ private static final byte[] WORLD_ARRAY = "world".getBytes(StandardCharsets.UTF_8);
+ private static final long HELLO_LONG = 2794345569481354659L;
+ private static final long WORLD_LONG = -1767385783675760145L;
+ private static final String EMPTY_HASH = "ef46db3751d8e999";
+ private static final String HELLO_HASH = "26c7827d889f6da3";
+ private static final String WORLD_HASH = "e778fbfe66ee51ef";
+ private static final String HELLO_CHECKSUM = "c07c10338a825a5d";
+ private static final String WORLD_CHECKSUM = "cb21505d7a714523";
+ private static final String FULL_CHECKSUM = "b8ca8fa824d335e9";
+
+ private static final HashAlgorithm ALGORITHM = XX.createAlgorithm();
+
+ @Test
+ public void testEmptyArray() {
+ byte[] emptyArray = new byte[0];
+ String hash = ALGORITHM.hash(emptyArray);
+ assertEquals(EMPTY_HASH, hash);
+ }
+
+ @Test
+ public void testSimpleHash() {
+ String helloHash = ALGORITHM.hash(HELLO_ARRAY);
+ assertEquals(HELLO_HASH, helloHash);
+
+ String worldHash = ALGORITHM.hash(WORLD_ARRAY);
+ assertEquals(WORLD_HASH, worldHash);
+ }
+
+ @Test
+ public void testSimpleChecksum() {
+ String helloChecksum = ALGORITHM.hash(longToBytes(1, HELLO_LONG));
+ assertEquals(HELLO_CHECKSUM, helloChecksum);
+
+ String worldChecksum = ALGORITHM.hash(longToBytes(1, WORLD_LONG));
+ assertEquals(WORLD_CHECKSUM, worldChecksum);
+
+ String checksum = ALGORITHM.hash(longToBytes(2, HELLO_LONG, WORLD_LONG));
+ assertEquals(FULL_CHECKSUM, checksum);
+ }
+
+ @Test
+ public void testEmptyBuffer() {
+ assertEmptyBuffer(XX.createChecksum(0));
+ assertEmptyBuffer(XXMM.createChecksum(0));
+ }
+
+ @Test
+ public void testSingleHash() {
+ assertSingleHash(XX.createChecksum(1));
+ assertSingleHash(XXMM.createChecksum(1));
+ }
+
+ @Test
+ public void testSingleChecksum() {
+ assertSingleChecksum(XX.createChecksum(1));
+ assertSingleChecksum(XXMM.createChecksum(1));
+ }
+
+ @Test
+ public void testNotFullChecksum() {
+ assertSingleChecksum(XX.createChecksum(2));
+ assertSingleChecksum(XXMM.createChecksum(2));
+ }
+
+ @Test
+ public void testFullChecksum() {
+ assertFullChecksum(XX.createChecksum(2));
+ assertFullChecksum(XXMM.createChecksum(2));
+ }
+
+ private void assertEmptyBuffer(HashChecksum checksum) {
+ assertEquals(EMPTY_HASH, checksum.digest());
+ }
+
+ private void assertSingleHash(HashChecksum checksum) {
+ assertEquals(HELLO_HASH, checksum.update(HELLO_ARRAY));
+ assertEquals(HELLO_CHECKSUM, checksum.digest());
+ }
+
+ private void assertSingleChecksum(HashChecksum checksum) {
+ assertEquals(HELLO_HASH, checksum.update(HELLO_HASH));
+ assertEquals(HELLO_CHECKSUM, checksum.digest());
+ }
+
+ private void assertFullChecksum(HashChecksum checksum) {
+ assertEquals(HELLO_HASH, checksum.update(HELLO_HASH));
+ assertEquals(WORLD_HASH, checksum.update(WORLD_HASH));
+ assertEquals(FULL_CHECKSUM, checksum.digest());
+ }
+
+ private byte[] longToBytes(int size, long... values) {
+ final ByteBuffer buffer = ByteBuffer.allocate(size * Long.SIZE / Byte.SIZE);
+ for (long value : values) {
+ buffer.putLong(value);
+ }
+ return buffer.array();
+ }
+}
diff --git a/maven-core/src/test/java/org/apache/maven/caching/hash/HexUtilsTest.java b/maven-core/src/test/java/org/apache/maven/caching/hash/HexUtilsTest.java
new file mode 100644
index 000000000000..e9615f493e20
--- /dev/null
+++ b/maven-core/src/test/java/org/apache/maven/caching/hash/HexUtilsTest.java
@@ -0,0 +1,50 @@
+package org.apache.maven.caching.hash;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+public class HexUtilsTest {
+
+ @Test
+ public void testEncodeToHex() {
+ //array length = 8 left padded with zeroes
+ assertEquals("0", HexUtils.encode(new byte[8]));
+ assertEquals("0", HexUtils.encode(new byte[1]));
+
+ assertEquals("a", HexUtils.encode(new byte[]{10}));
+ assertEquals("a", HexUtils.encode(new byte[]{0, 0, 0, 0, 0, 0, 0, 10}));
+
+ assertEquals("100", HexUtils.encode(new byte[]{1, 0}));
+ assertEquals("101", HexUtils.encode(new byte[]{0, 0, 0, 0, 0, 0, 1, 1}));
+ }
+
+ @Test
+ public void testDecodeHex() {
+ assertArrayEquals(new byte[]{0}, HexUtils.decode("0"));
+ assertArrayEquals(new byte[]{10}, HexUtils.decode("a"));
+ assertArrayEquals(new byte[]{10}, HexUtils.decode("A"));
+ assertArrayEquals(new byte[]{1, 0}, HexUtils.decode("100"));
+ }
+
+}
\ No newline at end of file
diff --git a/maven-core/src/test/java/org/apache/maven/caching/xml/CacheConfigTest.java b/maven-core/src/test/java/org/apache/maven/caching/xml/CacheConfigTest.java
new file mode 100644
index 000000000000..b18752fd0594
--- /dev/null
+++ b/maven-core/src/test/java/org/apache/maven/caching/xml/CacheConfigTest.java
@@ -0,0 +1,35 @@
+package org.apache.maven.caching.xml;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.junit.Test;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class CacheConfigTest {
+
+ @Test
+ public void testMU() {
+// final Path path = Paths.get("W:\\dev\\abfx\\maven-cache-config.xml");
+// final CacheConfig cacheConfig = CacheConfig.fromFile(path.toFile());
+// System.out.println(cacheConfig.toString());
+ }
+}
\ No newline at end of file
diff --git a/maven-core/src/test/java/org/apache/maven/lifecycle/internal/stub/MojoExecutorStub.java b/maven-core/src/test/java/org/apache/maven/lifecycle/internal/stub/MojoExecutorStub.java
index 8a6580b699af..1650e24eb681 100644
--- a/maven-core/src/test/java/org/apache/maven/lifecycle/internal/stub/MojoExecutorStub.java
+++ b/maven-core/src/test/java/org/apache/maven/lifecycle/internal/stub/MojoExecutorStub.java
@@ -17,10 +17,7 @@
import org.apache.maven.execution.MavenSession;
import org.apache.maven.lifecycle.LifecycleExecutionException;
-import org.apache.maven.lifecycle.internal.DependencyContext;
-import org.apache.maven.lifecycle.internal.MojoExecutor;
-import org.apache.maven.lifecycle.internal.PhaseRecorder;
-import org.apache.maven.lifecycle.internal.ProjectIndex;
+import org.apache.maven.lifecycle.internal.*;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.plugin.descriptor.PluginDescriptor;
@@ -39,8 +36,8 @@ public class MojoExecutorStub
public List executions = Collections.synchronizedList( new ArrayList() );
@Override
- public void execute( MavenSession session, MojoExecution mojoExecution, ProjectIndex projectIndex,
- DependencyContext dependencyContext, PhaseRecorder phaseRecorder )
+ public void execute(MavenSession session, MojoExecution mojoExecution, ProjectIndex projectIndex,
+ IDependencyContext dependencyContext, PhaseRecorder phaseRecorder )
throws LifecycleExecutionException
{
executions.add( mojoExecution );
diff --git a/pom.xml b/pom.xml
index c770f0d33c5c..75e04d8204bd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -47,8 +47,8 @@ under the License.
3.0.5
- 1.7
- 1.7
+ 1.8
+ 1.82.6.01.43.8.1
@@ -565,6 +565,11 @@ under the License.
-->
src/main/appended-resources/licenses/CDDL-1.0.txtsrc/main/appended-resources/licenses/EPL-1.0.txt
+ src/main/appended-resources/licenses/EDL-1.0.txt
+ src/main/appended-resources/licenses/MPL-1.1.txt
+ src/main/appended-resources/licenses/LGPL-3.0.txt
+ .editorconfig
+ **/*.iml
@@ -601,6 +606,7 @@ under the License.
animal-sniffer-maven-plugin1.17
+ falseorg.codehaus.mojo.signaturejava17