diff --git a/ezkv-maven-plugin/pom.xml b/ezkv-maven-plugin/pom.xml
new file mode 100644
index 0000000..fcd1fcc
--- /dev/null
+++ b/ezkv-maven-plugin/pom.xml
@@ -0,0 +1,37 @@
+
+ 4.0.0
+
+ io.jstach.ezkv
+ ezkv-maven-parent
+ 0.3.0-SNAPSHOT
+
+ ezkv-maven-plugin
+ maven-plugin
+
+ ../
+ ${basedir}/..
+ false
+
+
+
+ io.jstach.ezkv
+ ezkv-kvs
+
+
+ org.apache.maven
+ maven-plugin-api
+
+
+ org.apache.maven
+ maven-core
+
+
+ org.apache.maven.plugin-tools
+ maven-plugin-annotations
+ provided
+ 3.15.1
+
+
+
\ No newline at end of file
diff --git a/ezkv-maven-plugin/src/main/java/io/jstach/ezkv/maven/ReadConfigMojo.java b/ezkv-maven-plugin/src/main/java/io/jstach/ezkv/maven/ReadConfigMojo.java
new file mode 100644
index 0000000..c8c063d
--- /dev/null
+++ b/ezkv-maven-plugin/src/main/java/io/jstach/ezkv/maven/ReadConfigMojo.java
@@ -0,0 +1,185 @@
+package io.jstach.ezkv.maven;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Properties;
+import java.util.Set;
+
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugin.logging.Log;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.project.MavenProject;
+import org.jspecify.annotations.Nullable;
+
+import io.jstach.ezkv.kvs.KeyValuesEnvironment;
+import io.jstach.ezkv.kvs.KeyValuesSystem;
+import io.jstach.ezkv.kvs.Variables;
+
+/**
+ * This plugin is similar to properties-maven-plugin except
+ * that it will use EZKV to load the properties. Unlike the Codehaus properties plugin
+ * this plugin allows chain loading of config.
+ *
+ * However like the Codehaus properties plugin this plugin has the following limitations:
+ *
+ *
+ * This plugin (as all other) is executed in the later phase - when project model is
+ * already build in memory.
+ *
+ *
+ * So properties read from external files by this plugin can not by used in project
+ * definitions in items like {@code }, {@code } and so on.
+ *
+ *
+ * Properties read by plugin in one module are not propagated to other modules or child
+ * projects.
+ *
+ *
+ * Properties are only available for other plugins in runtime like for
+ * maven-resource-plugin for filtering resources.
+ *
+ */
+@Mojo(name = "read-config", defaultPhase = LifecyclePhase.NONE, requiresProject = true, threadSafe = true)
+public class ReadConfigMojo extends AbstractMojo {
+
+ @Parameter(defaultValue = "${project}", readonly = true, required = true)
+ private @Nullable MavenProject project;
+
+ @Parameter(defaultValue = "${session}", readonly = true, required = true)
+ private @Nullable MavenSession session;
+
+ /**
+ * Maven will call this constructor.
+ */
+ public ReadConfigMojo() {
+ }
+
+ /**
+ * The URLs that will be used when reading properties. These may be non-standard URLs
+ * of the form classpath:com/company/resource.properties
. Note that the
+ * type is not URL
for this reason and therefore will be explicitly
+ * checked by this Mojo.
+ */
+ @Parameter(required = true)
+ private String[] urls = new String[0];
+
+ /**
+ * Default scope for test access.
+ * @param urls The URLs to set for tests.
+ */
+ public void setUrls(String[] urls) {
+ if (urls == null) {
+ this.urls = null;
+ }
+ else {
+ this.urls = new String[urls.length];
+ System.arraycopy(urls, 0, this.urls, 0, urls.length);
+ }
+ }
+
+ private static T requireInjected(@Nullable T obj, String message) throws MojoExecutionException {
+ if (obj == null) {
+ throw new MojoExecutionException("Missing injection '" + message + "'");
+ }
+ return obj;
+ }
+
+ @Override
+ public void execute() throws MojoExecutionException, MojoFailureException {
+ String[] _urls = this.urls;
+ if (_urls == null || _urls.length < 1) {
+ throw new MojoExecutionException("urls not set");
+ }
+ var log = getLog();
+ var logger = new MavenLogger(log);
+ var session = requireInjected(this.session, "session");
+ var project = requireInjected(this.project, "project");
+ KeyValuesEnvironment env = new KeyValuesEnvironment() {
+ @Override
+ public Properties getSystemProperties() {
+ return session.getSystemProperties();
+ }
+
+ @Override
+ public KeyValuesEnvironment.Logger getLogger() {
+ return logger;
+ }
+ };
+ var loader = KeyValuesSystem.builder()
+ .useServiceLoader() //
+ .environment(env) //
+ .build()
+ .loader();
+
+ final Variables missingSystemPropeties = new Variables() {
+ final Set requiredSystemProperties = Collections.singleton("user.home");
+
+ @Override
+ public @Nullable String getValue(String key) {
+ if (requiredSystemProperties.contains(key)) {
+ getLog().info("Using real system property as it was not in maven's system properties. key=" + key);
+ return System.getProperty(key);
+ }
+ return null;
+ }
+ };
+ loader.add(missingSystemPropeties);
+ loader.variables(Variables::ofSystemEnv);
+ loader.variables(Variables::ofSystemProperties);
+ Properties originalProperties = project.getProperties();
+ // TODO maybe a bad idea?
+ loader.add(originalProperties::getProperty);
+
+ for (String url : _urls) {
+ loader.add(url);
+ }
+ try {
+ var kvs = loader.load().memoize();
+ if (log.isDebugEnabled()) {
+ getLog().debug("Found keys: " + kvs);
+ }
+ // TODO should we do some sort of lock here?
+ // Technically properties is synchronized
+ for (var kv : kvs) {
+ originalProperties.put(kv.key(), kv.value());
+ }
+
+ }
+ catch (IOException e) {
+ throw new MojoExecutionException("failed to load config", e);
+ }
+
+ }
+
+ record MavenLogger(Log log) implements KeyValuesEnvironment.Logger {
+
+ @Override
+ public void debug(String message) {
+ if (log.isDebugEnabled()) {
+ log.debug(message);
+ }
+ }
+
+ @Override
+ public void info(String message) {
+ if (log.isInfoEnabled()) {
+ log.info(message);
+ }
+ }
+
+ @Override
+ public void warn(String message) {
+ if (log.isWarnEnabled()) {
+ log.warn(message);
+ }
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/ezkv-maven-plugin/src/main/java/io/jstach/ezkv/maven/package-info.java b/ezkv-maven-plugin/src/main/java/io/jstach/ezkv/maven/package-info.java
new file mode 100644
index 0000000..02bf985
--- /dev/null
+++ b/ezkv-maven-plugin/src/main/java/io/jstach/ezkv/maven/package-info.java
@@ -0,0 +1,9 @@
+/**
+ * EZKV Maven Plugin to load configuration during Maven build and is analogous to
+ * properties-maven-plugin
+ * except that it will use EZKV to load the properties. Unlike the Codehaus properties
+ * plugin this plugin allows chain loading of config.
+ * @see io.jstach.ezkv.maven.ReadConfigMojo
+ */
+@org.jspecify.annotations.NullMarked
+package io.jstach.ezkv.maven;
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index f71e30b..a4e1390 100644
--- a/pom.xml
+++ b/pom.xml
@@ -85,6 +85,13 @@
org.apache.maven
maven-core
${maven.core.version}
+ provided
+
+
+ org.apache.maven
+ maven-plugin-api
+ ${maven.core.version}
+ provided
org.jspecify
@@ -977,5 +984,6 @@
ezkv-boot
ezkv-dotenv
ezkv-json5
+ ezkv-maven-plugin