diff --git a/nifi-extension-bundles/nifi-flow-registry-client-bundle/nifi-flow-registry-client-services/src/main/java/org/apache/nifi/registry/flow/NifiRegistryFlowRegistryClient.java b/nifi-extension-bundles/nifi-flow-registry-client-bundle/nifi-flow-registry-client-services/src/main/java/org/apache/nifi/registry/flow/NifiRegistryFlowRegistryClient.java
index 4c82e0eaf584..c072428e7aa7 100644
--- a/nifi-extension-bundles/nifi-flow-registry-client-bundle/nifi-flow-registry-client-services/src/main/java/org/apache/nifi/registry/flow/NifiRegistryFlowRegistryClient.java
+++ b/nifi-extension-bundles/nifi-flow-registry-client-bundle/nifi-flow-registry-client-services/src/main/java/org/apache/nifi/registry/flow/NifiRegistryFlowRegistryClient.java
@@ -95,7 +95,9 @@ private String getProposedUri(final FlowRegistryClientConfigurationContext conte
final URI fullUri = URI.create(configuredUrl);
final int port = fullUri.getPort();
final String portSuffix = port < 0 ? "" : ":" + port;
- final String uriString = fullUri.getScheme() + "://" + fullUri.getHost() + portSuffix;
+ final String path = fullUri.getPath();
+ final String pathSuffix = path == null ? "" : path;
+ final String uriString = fullUri.getScheme() + "://" + fullUri.getHost() + portSuffix + pathSuffix;
uri = URI.create(uriString);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("The given Registry URL is not valid: " + configuredUrl);
diff --git a/nifi-extension-bundles/nifi-flow-registry-client-bundle/nifi-flow-registry-client-services/src/test/java/org/apache/nifi/registry/flow/TestNifiRegistryFlowRegistryClient.java b/nifi-extension-bundles/nifi-flow-registry-client-bundle/nifi-flow-registry-client-services/src/test/java/org/apache/nifi/registry/flow/TestNifiRegistryFlowRegistryClient.java
new file mode 100644
index 000000000000..b0fe5748c739
--- /dev/null
+++ b/nifi-extension-bundles/nifi-flow-registry-client-bundle/nifi-flow-registry-client-services/src/test/java/org/apache/nifi/registry/flow/TestNifiRegistryFlowRegistryClient.java
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+package org.apache.nifi.registry.flow;
+
+import org.apache.nifi.components.PropertyValue;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Method;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class TestNifiRegistryFlowRegistryClient {
+
+ @Test
+ public void testGetProposedUri() throws Exception {
+ final NifiRegistryFlowRegistryClient client = new NifiRegistryFlowRegistryClient();
+ final FlowRegistryClientConfigurationContext context = mock(FlowRegistryClientConfigurationContext.class);
+ final PropertyValue propertyValue = mock(PropertyValue.class);
+
+ // Test with root context
+ when(context.getProperty(NifiRegistryFlowRegistryClient.PROPERTY_URL)).thenReturn(propertyValue);
+ when(propertyValue.evaluateAttributeExpressions()).thenReturn(propertyValue);
+ when(propertyValue.getValue()).thenReturn("https://localhost:18443");
+
+ // Access private method via reflection
+ Method getProposedUri = NifiRegistryFlowRegistryClient.class.getDeclaredMethod("getProposedUri", FlowRegistryClientConfigurationContext.class);
+ getProposedUri.setAccessible(true);
+ String uri = (String) getProposedUri.invoke(client, context);
+
+ assertEquals("https://localhost:18443", uri);
+
+ // Test with custom context path
+ when(propertyValue.getValue()).thenReturn("https://localhost:18443/my-registry");
+ uri = (String) getProposedUri.invoke(client, context);
+
+ // Before fix, this returns "https://localhost:18443" (stripped path)
+ // We assert expected behavior (preserving path) to confirm failure
+ assertEquals("https://localhost:18443/my-registry", uri);
+
+ // Test with trailing slash
+ when(propertyValue.getValue()).thenReturn("https://localhost:18443/my-registry/");
+ uri = (String) getProposedUri.invoke(client, context);
+ // URI.create(s).toString() normalizes? No, URI toString returns as is usually,
+ // but the method constructs a new URI.
+ // The current implementation reconstructs URI from scheme, host, port.
+ // My proposed fix will append path.
+ }
+}
diff --git a/nifi-framework-bundle/nifi-framework-extensions/nifi-framework-kubernetes-bundle/nifi-framework-kubernetes-state-provider/pom.xml b/nifi-framework-bundle/nifi-framework-extensions/nifi-framework-kubernetes-bundle/nifi-framework-kubernetes-state-provider/pom.xml
index 0cc8b076e1b9..5f98d312062a 100644
--- a/nifi-framework-bundle/nifi-framework-extensions/nifi-framework-kubernetes-bundle/nifi-framework-kubernetes-state-provider/pom.xml
+++ b/nifi-framework-bundle/nifi-framework-extensions/nifi-framework-kubernetes-bundle/nifi-framework-kubernetes-state-provider/pom.xml
@@ -26,6 +26,15 @@
org.apache.nifinifi-framework-api
+
+ org.apache.nifi
+ nifi-api
+
+
+ org.apache.nifi
+ nifi-utils
+ 2.4.0
+ org.apache.nifinifi-kubernetes-client
diff --git a/nifi-framework-bundle/nifi-framework-extensions/nifi-framework-kubernetes-bundle/nifi-framework-kubernetes-state-provider/src/main/java/org/apache/nifi/kubernetes/state/provider/KubernetesConfigMapStateProvider.java b/nifi-framework-bundle/nifi-framework-extensions/nifi-framework-kubernetes-bundle/nifi-framework-kubernetes-state-provider/src/main/java/org/apache/nifi/kubernetes/state/provider/KubernetesConfigMapStateProvider.java
index 0f1f545ac424..502fa4ea593a 100644
--- a/nifi-framework-bundle/nifi-framework-extensions/nifi-framework-kubernetes-bundle/nifi-framework-kubernetes-state-provider/src/main/java/org/apache/nifi/kubernetes/state/provider/KubernetesConfigMapStateProvider.java
+++ b/nifi-framework-bundle/nifi-framework-extensions/nifi-framework-kubernetes-bundle/nifi-framework-kubernetes-state-provider/src/main/java/org/apache/nifi/kubernetes/state/provider/KubernetesConfigMapStateProvider.java
@@ -24,6 +24,8 @@
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.dsl.Resource;
+import org.apache.nifi.annotation.documentation.CapabilityDescription;
+import org.apache.nifi.annotation.documentation.Tags;
import org.apache.nifi.components.AbstractConfigurableComponent;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.PropertyValue;
@@ -54,6 +56,10 @@
/**
* State Provider implementation based on Kubernetes ConfigMaps with Base64 encoded keys to meet Kubernetes constraints
*/
+@Tags({"kubernetes", "state", "provider", "configmap", "cluster"})
+@CapabilityDescription("Provides a State Provider that stores state in a Kubernetes ConfigMap. "
+ + "This allows state to be shared across a Kubernetes cluster. "
+ + "The State Provider uses the Kubernetes Client to read and write ConfigMaps.")
public class KubernetesConfigMapStateProvider extends AbstractConfigurableComponent implements StateProvider {
static final PropertyDescriptor CONFIG_MAP_NAME_PREFIX = new PropertyDescriptor.Builder()
.name("ConfigMap Name Prefix")
diff --git a/nifi-registry/nifi-registry-assembly/pom.xml b/nifi-registry/nifi-registry-assembly/pom.xml
index 9dafaba91d44..ad7824f51311 100644
--- a/nifi-registry/nifi-registry-assembly/pom.xml
+++ b/nifi-registry/nifi-registry-assembly/pom.xml
@@ -157,6 +157,7 @@
h2 http/1.1
+ ./work/jetty200true
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-jetty/src/main/java/org/apache/nifi/registry/jetty/JettyServer.java b/nifi-registry/nifi-registry-core/nifi-registry-jetty/src/main/java/org/apache/nifi/registry/jetty/JettyServer.java
index a3a165635c6f..65d3d1f04bb8 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-jetty/src/main/java/org/apache/nifi/registry/jetty/JettyServer.java
+++ b/nifi-registry/nifi-registry-core/nifi-registry-jetty/src/main/java/org/apache/nifi/registry/jetty/JettyServer.java
@@ -155,6 +155,10 @@ public void stop() {
}
protected List getApplicationUrls() {
+ // Get the context path prefix and build the full application path
+ final String contextPathPrefix = properties.getWebContextPath();
+ final String applicationPath = contextPathPrefix + APPLICATION_PATH;
+
return Arrays.stream(server.getConnectors())
.map(connector -> (ServerConnector) connector)
.map(serverConnector -> {
@@ -164,7 +168,7 @@ protected List getApplicationUrls() {
final String connectorHost = serverConnector.getHost();
final String host = StringUtils.defaultIfEmpty(connectorHost, HOST_UNSPECIFIED);
try {
- return new URI(scheme, null, host, port, APPLICATION_PATH, null, null);
+ return new URI(scheme, null, host, port, applicationPath, null, null);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-jetty/src/main/java/org/apache/nifi/registry/jetty/handler/StandardHandlerProvider.java b/nifi-registry/nifi-registry-core/nifi-registry-jetty/src/main/java/org/apache/nifi/registry/jetty/handler/StandardHandlerProvider.java
index 7a30556ae1a0..9ad25625591d 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-jetty/src/main/java/org/apache/nifi/registry/jetty/handler/StandardHandlerProvider.java
+++ b/nifi-registry/nifi-registry-core/nifi-registry-jetty/src/main/java/org/apache/nifi/registry/jetty/handler/StandardHandlerProvider.java
@@ -99,21 +99,27 @@ public Handler getHandler(final NiFiRegistryProperties properties) {
final File libDirectory = properties.getWarLibDirectory();
final File workDirectory = properties.getWebWorkingDirectory();
+ // Get the context path prefix (e.g., "/integration-hub") and normalize it
+ final String contextPathPrefix = properties.getWebContextPath();
+ final String uiContextPath = contextPathPrefix + UI_CONTEXT_PATH;
+ final String apiContextPath = contextPathPrefix + API_CONTEXT_PATH;
+ final String docsContextPath = contextPathPrefix + DOCS_CONTEXT_PATH;
+
final Handler.Collection handlers = new ContextHandlerCollection();
// Add Header Writer Handler before others
handlers.addHandler(new HeaderWriterHandler());
- final WebAppContext userInterfaceContext = getWebAppContext(libDirectory, workDirectory, ClassLoader.getSystemClassLoader(), UI_FILE_PATTERN, UI_CONTEXT_PATH);
+ final WebAppContext userInterfaceContext = getWebAppContext(libDirectory, workDirectory, ClassLoader.getSystemClassLoader(), UI_FILE_PATTERN, uiContextPath);
userInterfaceContext.setInitParameter(OIDC_SUPPORTED_PARAMETER, Boolean.toString(properties.isOidcEnabled()));
handlers.addHandler(userInterfaceContext);
final ClassLoader apiClassLoader = getApiClassLoader(properties.getDatabaseDriverDirectory());
- final WebAppContext apiContext = getWebAppContext(libDirectory, workDirectory, apiClassLoader, API_FILE_PATTERN, API_CONTEXT_PATH);
+ final WebAppContext apiContext = getWebAppContext(libDirectory, workDirectory, apiClassLoader, API_FILE_PATTERN, apiContextPath);
apiContext.setAttribute(PROPERTIES_PARAMETER, properties);
apiContext.setThrowUnavailableOnStartupException(true);
handlers.addHandler(apiContext);
- final WebAppContext docsContext = getWebAppContext(libDirectory, workDirectory, ClassLoader.getSystemClassLoader(), DOCS_FILE_PATTERN, DOCS_CONTEXT_PATH);
+ final WebAppContext docsContext = getWebAppContext(libDirectory, workDirectory, ClassLoader.getSystemClassLoader(), DOCS_FILE_PATTERN, docsContextPath);
final Path docsDir = getDocsDirectory();
final ServletHolder docsServletHolder = getDocsServletHolder(docsDir);
docsContext.addServlet(docsServletHolder, HTML_DOCS_PATH);
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/NiFiRegistryProperties.java b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/NiFiRegistryProperties.java
index 554e13024063..79a200b717bf 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/NiFiRegistryProperties.java
+++ b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/NiFiRegistryProperties.java
@@ -59,6 +59,7 @@ public class NiFiRegistryProperties extends ApplicationProperties {
public static final String WEB_WORKING_DIR = "nifi.registry.web.jetty.working.directory";
public static final String WEB_THREADS = "nifi.registry.web.jetty.threads";
+ public static final String WEB_CONTEXT_PATH = "nifi.registry.web.context.path";
public static final String WEB_SHOULD_SEND_SERVER_VERSION = "nifi.registry.web.should.send.server.version";
public static final String SECURITY_KEYSTORE = "nifi.registry.security.keystore";
@@ -514,4 +515,30 @@ public Set getHttpsNetworkInterfaceNames() {
}
return networkInterfaceNames;
}
+
+ /**
+ * Returns the web context path prefix. The path will be normalized to ensure it starts with a leading slash
+ * and does not end with a trailing slash. Returns an empty string if not configured.
+ *
+ * @return the normalized context path prefix (e.g., "/integration-hub")
+ */
+ public String getWebContextPath() {
+ final String contextPath = getProperty(WEB_CONTEXT_PATH);
+ if (StringUtils.isBlank(contextPath)) {
+ return "";
+ }
+ return normalizeContextPath(contextPath.trim());
+ }
+
+ private String normalizeContextPath(String cp) {
+ if (cp == null || cp.equalsIgnoreCase("")) {
+ return "";
+ } else {
+ String trimmedCP = cp.trim();
+ // Ensure it starts with a leading slash and does not end in a trailing slash
+ trimmedCP = trimmedCP.startsWith("/") ? trimmedCP : "/" + trimmedCP;
+ trimmedCP = trimmedCP.endsWith("/") ? trimmedCP.substring(0, trimmedCP.length() - 1) : trimmedCP;
+ return trimmedCP;
+ }
+ }
}
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/java/org/apache/nifi/registry/properties/TestNiFiRegistryPropertiesContextPath.java b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/java/org/apache/nifi/registry/properties/TestNiFiRegistryPropertiesContextPath.java
new file mode 100644
index 000000000000..3a6fe985dfdf
--- /dev/null
+++ b/nifi-registry/nifi-registry-core/nifi-registry-properties/src/test/java/org/apache/nifi/registry/properties/TestNiFiRegistryPropertiesContextPath.java
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+package org.apache.nifi.registry.properties;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+public class TestNiFiRegistryPropertiesContextPath {
+
+ @Test
+ public void testGetWebContextPathNotSet() {
+ final NiFiRegistryProperties properties = new NiFiRegistryProperties();
+ final String contextPath = properties.getWebContextPath();
+ assertNotNull(contextPath);
+ assertEquals("", contextPath);
+ }
+
+ @Test
+ public void testGetWebContextPathWithLeadingSlash() {
+ final Map props = new HashMap<>();
+ props.put(NiFiRegistryProperties.WEB_CONTEXT_PATH, "/integration-hub");
+ final NiFiRegistryProperties properties = new NiFiRegistryProperties(props);
+ final String contextPath = properties.getWebContextPath();
+ assertEquals("/integration-hub", contextPath);
+ }
+
+ @Test
+ public void testGetWebContextPathWithoutLeadingSlash() {
+ final Map props = new HashMap<>();
+ props.put(NiFiRegistryProperties.WEB_CONTEXT_PATH, "integration-hub");
+ final NiFiRegistryProperties properties = new NiFiRegistryProperties(props);
+ final String contextPath = properties.getWebContextPath();
+ assertEquals("/integration-hub", contextPath);
+ }
+
+ @Test
+ public void testGetWebContextPathWithTrailingSlash() {
+ final Map props = new HashMap<>();
+ props.put(NiFiRegistryProperties.WEB_CONTEXT_PATH, "/integration-hub/");
+ final NiFiRegistryProperties properties = new NiFiRegistryProperties(props);
+ final String contextPath = properties.getWebContextPath();
+ assertEquals("/integration-hub", contextPath);
+ }
+
+ @Test
+ public void testGetWebContextPathWithBothSlashes() {
+ final Map props = new HashMap<>();
+ props.put(NiFiRegistryProperties.WEB_CONTEXT_PATH, "integration-hub/");
+ final NiFiRegistryProperties properties = new NiFiRegistryProperties(props);
+ final String contextPath = properties.getWebContextPath();
+ assertEquals("/integration-hub", contextPath);
+ }
+
+ @Test
+ public void testGetWebContextPathWithWhitespace() {
+ final Map props = new HashMap<>();
+ props.put(NiFiRegistryProperties.WEB_CONTEXT_PATH, " /integration-hub ");
+ final NiFiRegistryProperties properties = new NiFiRegistryProperties(props);
+ final String contextPath = properties.getWebContextPath();
+ assertEquals("/integration-hub", contextPath);
+ }
+
+ @Test
+ public void testGetWebContextPathEmptyString() {
+ final Map props = new HashMap<>();
+ props.put(NiFiRegistryProperties.WEB_CONTEXT_PATH, "");
+ final NiFiRegistryProperties properties = new NiFiRegistryProperties(props);
+ final String contextPath = properties.getWebContextPath();
+ assertEquals("", contextPath);
+ }
+}
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/nifi-registry.properties b/nifi-registry/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/nifi-registry.properties
index a0c35aa8044a..df9536df374d 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/nifi-registry.properties
+++ b/nifi-registry/nifi-registry-core/nifi-registry-resources/src/main/resources/conf/nifi-registry.properties
@@ -21,6 +21,7 @@ nifi.registry.web.https.host=${nifi.registry.web.https.host}
nifi.registry.web.https.port=${nifi.registry.web.https.port}
nifi.registry.web.https.network.interface.default=${nifi.registry.web.https.network.interface.default}
nifi.registry.web.https.application.protocols=${nifi.registry.web.https.application.protocols}
+nifi.registry.web.context.path=${nifi.registry.web.context.path}
nifi.registry.web.jetty.working.directory=${nifi.registry.jetty.work.dir}
nifi.registry.web.jetty.threads=${nifi.registry.web.jetty.threads}
nifi.registry.web.should.send.server.version=${nifi.registry.web.should.send.server.version}
diff --git a/nifi-registry/nifi-registry-core/nifi-registry-web-docs/src/main/webapp/WEB-INF/jsp/documentation.jsp b/nifi-registry/nifi-registry-core/nifi-registry-web-docs/src/main/webapp/WEB-INF/jsp/documentation.jsp
index 592039fc6a09..ffded29b452c 100644
--- a/nifi-registry/nifi-registry-core/nifi-registry-web-docs/src/main/webapp/WEB-INF/jsp/documentation.jsp
+++ b/nifi-registry/nifi-registry-core/nifi-registry-web-docs/src/main/webapp/WEB-INF/jsp/documentation.jsp
@@ -20,7 +20,7 @@
-
+
NiFi Registry Documentation
diff --git a/readme b/readme
new file mode 100644
index 000000000000..34b3685af4af
--- /dev/null
+++ b/readme
@@ -0,0 +1,8 @@
+Build the NiFi Source First, you need to compile the source code you just patched so that it generates those .zip files. From the root (C:\MyData\Test\nifi-2.4.0-source-release\original-repo\nifi), run:
+
+Run - mvn clean install -DskipTests
+(Note: This might take ~10-15 minutes since NiFi is huge. It will create nifi-assembly/target/nifi-2.4.0-bin.zip and nifi-toolkit/nifi-toolkit-assembly/target/nifi-toolkit-2.4.0-bin.zip)
+
+Move the Zips to your Docker folder Copy those two generated .zip files into your nifi-docker/dockerhub/ folder so Docker can see them.
+
+docker build -f Dockerfile-local -t nifi:2.4.0-custom .
\ No newline at end of file